Completed
Pull Request — master (#31)
by Chad
01:29
created

Reader::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 3
1
<?php
2
3
namespace SubjectivePHP\Csv;
4
5
use SplFileObject;
6
7
/**
8
 * Simple class for reading delimited data files
9
 */
10
class Reader implements \Iterator
11
{
12
    /**
13
     * The column headers.
14
     *
15
     * @var array
16
     */
17
    private $headers;
18
19
    /**
20
     * The current file pointer position.
21
     *
22
     * @var integer
23
     */
24
    private $position = 0;
25
26
    /**
27
     * The current row within the csv file.
28
     *
29
     * @var array|false|null
30
     */
31
    private $current = null;
32
33
    /**
34
     * @var SplFileObject
35
     */
36
    private $fileObject;
37
38
    /**
39
     * Create a new Reader instance.
40
     *
41
     * @param string                  $file           The full path to the csv file.
42
     * @param HeaderStrategyInterface $headerStrategy Strategy for obtaining headers of the file.
43
     * @param CsvOptions              $csvOptions     Options for the csv file.
44
     *
45
     * @throws \InvalidArgumentException Thrown if $file is not readable.
46
     */
47
    public function __construct($file, HeaderStrategyInterface $headerStrategy = null, CsvOptions $csvOptions = null)
48
    {
49
        if (!is_readable((string)$file)) {
50
            throw new \InvalidArgumentException(
51
                '$file must be a string containing a full path to a readable delimited file'
52
            );
53
        }
54
55
        $csvOptions = $csvOptions ?? new CsvOptions();
56
        $headerStrategy = $headerStrategy ?? new DeriveHeaderStrategy();
57
58
        $this->fileObject = new SplFileObject($file);
59
        $this->fileObject->setFlags(SplFileObject::READ_CSV);
60
        $this->fileObject->setCsvControl(
61
            $csvOptions->getDelimiter(),
62
            $csvOptions->getEnclosure(),
63
            $csvOptions->getEscapeChar()
64
        );
65
66
        $this->headers = $headerStrategy->getHeaders($this->fileObject);
67
    }
68
69
    /**
70
     * Advances to the next row in this csv reader
71
     *
72
     * @return mixed
73
     */
74
    public function next()
75
    {
76
        try {
77
            $raw = $this->readLine();
78
            if ($this->current !== null) {
79
                ++$this->position;
80
                $this->current = array_combine($this->headers, $raw);
81
            }
82
83
            //Headers given, skip first line if header line
84
            if ($raw === $this->headers) {
85
                $raw = $this->readLine();
86
            }
87
88
            $this->current = array_combine($this->headers, $raw);
89
        } catch (\Exception $e) {
90
            $this->current = false;
91
            return false;
92
        }
93
    }
94
95
    /**
96
     * Helper method to read the next line in the delimited file.
97
     *
98
     * @return array|false
99
     *
100
     * @throws \Exception Thrown if no data is returned when reading the file.
101
     */
102
    private function readLine()
103
    {
104
        $raw = $this->fileObject->fgetcsv();
105
        if (empty($raw)) {
106
            throw new \Exception('Empty line read');
107
        }
108
109
        return $raw;
110
    }
111
112
    /**
113
     * Return the current element.
114
     *
115
     * @return array returns array containing values from the current row
116
     */
117
    public function current()
118
    {
119
        if ($this->current === null) {
120
            $this->next();
121
        }
122
123
        return $this->current;
124
    }
125
126
    /**
127
     * Return the key of the current element.
128
     *
129
     * @return integer
130
     */
131
    public function key()
132
    {
133
        return $this->position;
134
    }
135
136
    /**
137
     * Rewind the Iterator to the first element.
138
     *
139
     * @return void
140
     */
141
    public function rewind()
142
    {
143
        $this->fileObject->rewind();
144
        $this->position = 0;
145
        $this->current = null;
146
    }
147
148
    /**
149
     * Check if there is a current element after calls to rewind() or next().
150
     *
151
     * @return bool true if there is a current element, false otherwise
152
     */
153
    public function valid()
154
    {
155
        if ($this->current === null) {
156
            $this->next();
157
        }
158
159
        return !$this->fileObject->eof() && $this->current !== false;
160
    }
161
162
    /**
163
     * Ensure file handles are closed when all references to this reader are destroyed.
164
     *
165
     * @return void
166
     */
167
    public function __destruct()
168
    {
169
        $this->fileObject = null;
170
    }
171
}
172