Passed
Push — 1.x ( 549b59 )
by Fabrice
08:51
created

CsvExtractor::getTraversable()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 7
nc 4
nop 1
dl 0
loc 16
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of YaEtl.
5
 *     (c) Fabrice de Stefanis / https://github.com/fab2s/YaEtl
6
 * This source file is licensed under the MIT license which you will
7
 * find in the LICENSE file or at https://opensource.org/licenses/MIT
8
 */
9
10
namespace fab2s\YaEtl\Extractors\File;
11
12
use fab2s\NodalFlow\NodalFlowException;
13
use fab2s\NodalFlow\YaEtlException;
14
use fab2s\YaEtl\Traits\CsvHandlerTrait;
15
16
/**
17
 * Class CsvExtractor
18
 */
19
class CsvExtractor extends FileExtractorAbstract
20
{
21
    use CsvHandlerTrait;
22
23
    /**
24
     * CsvExtractor constructor
25
     *
26
     * @param resource|string $input
27
     * @param string          $delimiter
28
     * @param string          $enclosure
29
     * @param string          $escape
30
     *
31
     * @throws NodalFlowException
32
     * @throws YaEtlException
33
     */
34
    public function __construct($input, $delimiter = ',', $enclosure = '"', $escape = '\\')
35
    {
36
        parent::__construct($input);
37
        $this->delimiter = $delimiter;
38
        $this->enclosure = $enclosure;
39
        $this->escape    = $escape;
40
    }
41
42
    /**
43
     * @param mixed $param
44
     *
45
     * @return \Generator
46
     */
47
    public function getTraversable($param = null)
48
    {
49
        while ($this->extract($param)) {
50
            if (!$this->readBom() || !$this->readSep() || false === ($firstRecord = $this->readHeader())) {
51
                return;
52
            }
53
54
            /* @var array $firstRecord */
55
            yield $this->bakeRecord($firstRecord);
56
            while (false !== ($record = $this->getNextNonEmptyRecord())) {
57
                /* @var array $record */
58
                yield $this->bakeRecord($record);
59
            }
60
        }
61
62
        $this->releaseHandle();
63
    }
64
65
    /**
66
     * @param array $record
67
     *
68
     * @return array
69
     */
70
    protected function bakeRecord(array $record)
71
    {
72
        return isset($this->header) ? array_combine($this->header, $record) : $record;
73
    }
74
75
    /**
76
     * @return bool
77
     */
78
    protected function readHeader()
79
    {
80
        if (false === ($firstRecord = $this->getNextNonEmptyRecord())) {
81
            return false;
82
        }
83
84
        if ($this->useHeader && !isset($this->header)) {
85
            $this->header = array_map('trim', $firstRecord);
86
87
            return $this->getNextNonEmptyRecord();
88
        }
89
90
        return $firstRecord;
91
    }
92
93
    /**
94
     * @return bool
95
     */
96
    protected function readSep()
97
    {
98
        if (false === ($firstChar = $this->getNextNonEmptyChars())) {
99
            return false;
100
        }
101
102
        $firstCharPos = ftell($this->handle);
103
        /* @var string $firstChar */
104
        if ($firstChar === 's') {
105
            if (false === ($chars = fread($this->handle, 4))) {
106
                return false;
107
            }
108
109
            /* @var string $chars */
110
            $line = $firstChar . $chars;
111
            if (strpos($line, 'sep=') === 0) {
112
                $this->useSep    = true;
113
                $this->delimiter = $line[4];
114
115
                return !fseek($this->handle, $firstCharPos + 5);
116
            }
117
        }
118
119
        return !fseek($this->handle, $firstCharPos - 1);
120
    }
121
122
    /**
123
     * @return array|false
124
     */
125
    protected function getNextNonEmptyRecord()
126
    {
127
        do {
128
            if (false === ($record = fgetcsv($this->handle, 0, $this->delimiter, $this->enclosure, $this->escape))) {
129
                return false;
130
            }
131
132
            if ($record === [null]) {
133
                // empty line
134
                continue;
135
            }
136
137
            return $record;
138
        } while (!feof($this->handle));
139
    }
140
}
141