Completed
Push — develop ( 8c5838...f74fde )
by Adrien
20:53
created

CSV::canRead()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 13
ccs 4
cts 6
cp 0.6667
crap 2.1481
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Spreadsheet;
6
7
/**
8
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23
 *
24
 * @category   PhpSpreadsheet
25
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
26
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
27
 * @version    ##VERSION##, ##DATE##
28
 */
29
class CSV extends BaseReader implements IReader
30
{
31
    /**
32
     * Input encoding
33
     *
34
     * @var    string
35
     */
36
    private $inputEncoding = 'UTF-8';
37
38
    /**
39
     * Delimiter
40
     *
41
     * @var string
42
     */
43
    private $delimiter = ',';
44
45
    /**
46
     * Enclosure
47
     *
48
     * @var    string
49
     */
50
    private $enclosure = '"';
51
52
    /**
53
     * Sheet index to read
54
     *
55
     * @var    int
56
     */
57
    private $sheetIndex = 0;
58
59
    /**
60
     * Load rows contiguously
61
     *
62
     * @var    bool
63
     */
64
    private $contiguous = false;
65
66
    /**
67
     * Row counter for loading rows contiguously
68
     *
69
     * @var    int
70
     */
71
    private $contiguousRow = -1;
72
73
    /**
74
     * Create a new CSV Reader instance
75
     */
76 1
    public function __construct()
77
    {
78 1
        $this->readFilter = new DefaultReadFilter();
79 1
    }
80
81
    /**
82
     * Set input encoding
83
     *
84
     * @param string $pValue Input encoding
85
     */
86
    public function setInputEncoding($pValue = 'UTF-8')
87
    {
88
        $this->inputEncoding = $pValue;
89
90
        return $this;
91
    }
92
93
    /**
94
     * Get input encoding
95
     *
96
     * @return string
97
     */
98
    public function getInputEncoding()
99
    {
100
        return $this->inputEncoding;
101
    }
102
103
    /**
104
     * Move filepointer past any BOM marker
105
     */
106 1
    protected function skipBOM()
107
    {
108 1
        rewind($this->fileHandle);
109
110 1
        switch ($this->inputEncoding) {
111 1 View Code Duplication
            case 'UTF-8':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
112 1
                fgets($this->fileHandle, 4) == "\xEF\xBB\xBF" ?
113 1
                    fseek($this->fileHandle, 3) : fseek($this->fileHandle, 0);
114 1
                break;
115 View Code Duplication
            case 'UTF-16LE':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
116
                fgets($this->fileHandle, 3) == "\xFF\xFE" ?
117
                    fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0);
118
                break;
119 View Code Duplication
            case 'UTF-16BE':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
120
                fgets($this->fileHandle, 3) == "\xFE\xFF" ?
121
                    fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0);
122
                break;
123 View Code Duplication
            case 'UTF-32LE':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
124
                fgets($this->fileHandle, 5) == "\xFF\xFE\x00\x00" ?
125
                    fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0);
126
                break;
127 View Code Duplication
            case 'UTF-32BE':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
128
                fgets($this->fileHandle, 5) == "\x00\x00\xFE\xFF" ?
129
                    fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0);
130
                break;
131
            default:
132
                break;
133
        }
134 1
    }
135
136
    /**
137
     * Identify any separator that is explicitly set in the file
138
     */
139 1
    protected function checkSeparator()
140
    {
141 1
        $line = fgets($this->fileHandle);
142 1
        if ($line === false) {
143
            return;
144
        }
145
146 1
        if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
147
            $this->delimiter = substr($line, 4, 1);
148
149
            return;
150
        }
151
152 1
        return $this->skipBOM();
153
    }
154
155
    /**
156
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
157
     *
158
     * @param     string         $pFilename
159
     * @throws    Exception
160
     */
161
    public function listWorksheetInfo($pFilename)
162
    {
163
        // Open file
164
        if (!$this->canRead($pFilename)) {
165
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
166
        }
167
        $this->openFile($pFilename);
168
        $fileHandle = $this->fileHandle;
169
170
        // Skip BOM, if any
171
        $this->skipBOM();
172
        $this->checkSeparator();
173
174
        $escapeEnclosures = ['\\' . $this->enclosure, $this->enclosure . $this->enclosure];
0 ignored issues
show
Unused Code introduced by
$escapeEnclosures is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
175
176
        $worksheetInfo = [];
177
        $worksheetInfo[0]['worksheetName'] = 'Worksheet';
178
        $worksheetInfo[0]['lastColumnLetter'] = 'A';
179
        $worksheetInfo[0]['lastColumnIndex'] = 0;
180
        $worksheetInfo[0]['totalRows'] = 0;
181
        $worksheetInfo[0]['totalColumns'] = 0;
182
183
        // Loop through each line of the file in turn
184
        while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure)) !== false) {
185
            ++$worksheetInfo[0]['totalRows'];
186
            $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
187
        }
188
189
        $worksheetInfo[0]['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex']);
190
        $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
191
192
        // Close file
193
        fclose($fileHandle);
194
195
        return $worksheetInfo;
196
    }
197
198
    /**
199
     * Loads Spreadsheet from file
200
     *
201
     * @param     string         $pFilename
202
     * @throws Exception
203
     * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
204
     */
205 1
    public function load($pFilename)
206
    {
207
        // Create new Spreadsheet
208 1
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
209
210
        // Load into this instance
211 1
        return $this->loadIntoExisting($pFilename, $spreadsheet);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadIntoEx...ilename, $spreadsheet); (PhpOffice\PhpSpreadsheet\Spreadsheet) is incompatible with the return type declared by the interface PhpOffice\PhpSpreadsheet\Reader\IReader::load of type PhpOffice\PhpSpreadsheet\Reader\PhpSpreadsheet.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
212
    }
213
214
    /**
215
     * Loads PhpSpreadsheet from file into PhpSpreadsheet instance
216
     *
217
     * @param string $pFilename
218
     * @param Spreadsheet $spreadsheet
219
     * @throws Exception
220
     * @return Spreadsheet
221
     */
222 1
    public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
223
    {
224 1
        $lineEnding = ini_get('auto_detect_line_endings');
225 1
        ini_set('auto_detect_line_endings', true);
226
227
        // Open file
228 1
        if (!$this->canRead($pFilename)) {
229
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
230
        }
231 1
        $this->openFile($pFilename);
232 1
        $fileHandle = $this->fileHandle;
233
234
        // Skip BOM, if any
235 1
        $this->skipBOM();
236 1
        $this->checkSeparator();
237
238
        // Create new PhpSpreadsheet object
239 1
        while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
240
            $spreadsheet->createSheet();
241
        }
242 1
        $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
243
244
        $escapeEnclosures = [
245 1
            '\\' . $this->enclosure,
246 1
            $this->enclosure . $this->enclosure,
247
        ];
248
249
        // Set our starting row based on whether we're in contiguous mode or not
250 1
        $currentRow = 1;
251 1
        if ($this->contiguous) {
252
            $currentRow = ($this->contiguousRow == -1) ? $sheet->getHighestRow() : $this->contiguousRow;
253
        }
254
255
        // Loop through each line of the file in turn
256 1
        while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure)) !== false) {
257 1
            $columnLetter = 'A';
258 1
            foreach ($rowData as $rowDatum) {
259 1
                if ($rowDatum != '' && $this->readFilter->readCell($columnLetter, $currentRow)) {
260
                    // Unescape enclosures
261 1
                    $rowDatum = str_replace($escapeEnclosures, $this->enclosure, $rowDatum);
262
263
                    // Convert encoding if necessary
264 1
                    if ($this->inputEncoding !== 'UTF-8') {
265
                        $rowDatum = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($rowDatum, 'UTF-8', $this->inputEncoding);
266
                    }
267
268
                    // Set cell value
269 1
                    $sheet->getCell($columnLetter . $currentRow)->setValue($rowDatum);
270
                }
271 1
                ++$columnLetter;
272
            }
273 1
            ++$currentRow;
274
        }
275
276
        // Close file
277 1
        fclose($fileHandle);
278
279 1
        if ($this->contiguous) {
280
            $this->contiguousRow = $currentRow;
0 ignored issues
show
Documentation Bug introduced by
It seems like $currentRow can also be of type string. However, the property $contiguousRow is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
281
        }
282
283 1
        ini_set('auto_detect_line_endings', $lineEnding);
284
285
        // Return
286 1
        return $spreadsheet;
287
    }
288
289
    /**
290
     * Get delimiter
291
     *
292
     * @return string
293
     */
294
    public function getDelimiter()
295
    {
296
        return $this->delimiter;
297
    }
298
299
    /**
300
     * Set delimiter
301
     *
302
     * @param    string    $pValue        Delimiter, defaults to ,
303
     * @return   CSV
304
     */
305 1
    public function setDelimiter($pValue = ',')
306
    {
307 1
        $this->delimiter = $pValue;
308
309 1
        return $this;
310
    }
311
312
    /**
313
     * Get enclosure
314
     *
315
     * @return string
316
     */
317
    public function getEnclosure()
318
    {
319
        return $this->enclosure;
320
    }
321
322
    /**
323
     * Set enclosure
324
     *
325
     * @param    string    $pValue        Enclosure, defaults to "
326
     * @return   CSV
327
     */
328 1
    public function setEnclosure($pValue = '"')
329
    {
330 1
        if ($pValue == '') {
331
            $pValue = '"';
332
        }
333 1
        $this->enclosure = $pValue;
334
335 1
        return $this;
336
    }
337
338
    /**
339
     * Get sheet index
340
     *
341
     * @return int
342
     */
343
    public function getSheetIndex()
344
    {
345
        return $this->sheetIndex;
346
    }
347
348
    /**
349
     * Set sheet index
350
     *
351
     * @param    int        $pValue        Sheet index
352
     * @return   CSV
353
     */
354 1
    public function setSheetIndex($pValue = 0)
355
    {
356 1
        $this->sheetIndex = $pValue;
357
358 1
        return $this;
359
    }
360
361
    /**
362
     * Set Contiguous
363
     *
364
     * @param bool $contiguous
365
     */
366
    public function setContiguous($contiguous = false)
367
    {
368
        $this->contiguous = (bool) $contiguous;
369
        if (!$contiguous) {
370
            $this->contiguousRow = -1;
371
        }
372
373
        return $this;
374
    }
375
376
    /**
377
     * Get Contiguous
378
     *
379
     * @return bool
380
     */
381
    public function getContiguous()
382
    {
383
        return $this->contiguous;
384
    }
385
386
    /**
387
     * Can the current IReader read the file?
388
     *
389
     * @param     string         $pFilename
390
     * @throws Exception
391
     * @return bool
392
     */
393 1
    public function canRead($pFilename)
394
    {
395
        // Check if file exists
396
        try {
397 1
            $this->openFile($pFilename);
398
        } catch (Exception $e) {
399
            return false;
400
        }
401
402 1
        fclose($this->fileHandle);
403
404 1
        return true;
405
    }
406
}
407