Failed Conditions
Pull Request — master (#31)
by Chad
01:33
created

src/Reader.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     * @var HeaderStrategyInterface
40
     */
41
    private $headerStrategy;
42
43
    /**
44
     * Create a new Reader instance.
45
     *
46
     * @param string                  $file           The full path to the csv file.
47
     * @param HeaderStrategyInterface $headerStrategy Strategy for obtaining headers of the file.
48
     * @param CsvOptions              $csvOptions     Options for the csv file.
49
     *
50
     * @throws \InvalidArgumentException Thrown if $file is not readable.
51
     */
52
    public function __construct($file, HeaderStrategyInterface $headerStrategy = null, CsvOptions $csvOptions = null)
53
    {
54
        if (!is_readable((string)$file)) {
55
            throw new \InvalidArgumentException(
56
                '$file must be a string containing a full path to a readable delimited file'
57
            );
58
        }
59
60
        $csvOptions = $csvOptions ?? new CsvOptions();
61
        $this->headerStrategy = $headerStrategy ?? new DeriveHeaderStrategy();
62
63
        $this->fileObject = new SplFileObject($file);
64
        $this->fileObject->setFlags(SplFileObject::READ_CSV);
65
        $this->fileObject->setCsvControl(
66
            $csvOptions->getDelimiter(),
67
            $csvOptions->getEnclosure(),
68
            $csvOptions->getEscapeChar()
69
        );
70
71
        $this->headers = $this->headerStrategy->getHeaders($this->fileObject);
72
    }
73
74
    /**
75
     * Advances to the next row in this csv reader
76
     *
77
     * @return mixed
78
     */
79
    public function next()
80
    {
81
        try {
82
            $raw = $this->readLine();
83
            if ($this->current !== null) {
84
                ++$this->position;
85
                $this->current = array_combine($this->headers, $raw);
86
            }
87
88
            //Headers given, skip first line if header line
89
            if ($this->headerStrategy->isHeaderRow($raw)) {
0 ignored issues
show
It seems like $raw defined by $this->readLine() on line 82 can also be of type false; however, SubjectivePHP\Csv\Header...nterface::isHeaderRow() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
90
                $raw = $this->readLine();
91
            }
92
93
            $this->current = array_combine($this->headers, $raw);
94
        } catch (\Exception $e) {
95
            $this->current = false;
96
            return false;
97
        }
98
    }
99
100
    /**
101
     * Helper method to read the next line in the delimited file.
102
     *
103
     * @return array|false
104
     *
105
     * @throws \Exception Thrown if no data is returned when reading the file.
106
     */
107
    private function readLine()
108
    {
109
        $raw = $this->fileObject->fgetcsv();
110
        if (empty($raw)) {
111
            throw new \Exception('Empty line read');
112
        }
113
114
        return $raw;
115
    }
116
117
    /**
118
     * Return the current element.
119
     *
120
     * @return array returns array containing values from the current row
121
     */
122
    public function current()
123
    {
124
        if ($this->current === null) {
125
            $this->next();
126
        }
127
128
        return $this->current;
129
    }
130
131
    /**
132
     * Return the key of the current element.
133
     *
134
     * @return integer
135
     */
136
    public function key()
137
    {
138
        return $this->position;
139
    }
140
141
    /**
142
     * Rewind the Iterator to the first element.
143
     *
144
     * @return void
145
     */
146
    public function rewind()
147
    {
148
        $this->fileObject->rewind();
149
        $this->position = 0;
150
        $this->current = null;
151
    }
152
153
    /**
154
     * Check if there is a current element after calls to rewind() or next().
155
     *
156
     * @return bool true if there is a current element, false otherwise
157
     */
158
    public function valid()
159
    {
160
        if ($this->current === null) {
161
            $this->next();
162
        }
163
164
        return !$this->fileObject->eof() && $this->current !== false;
165
    }
166
167
    /**
168
     * Ensure file handles are closed when all references to this reader are destroyed.
169
     *
170
     * @return void
171
     */
172
    public function __destruct()
173
    {
174
        $this->fileObject = null;
175
    }
176
}
177