CsvExtractor::getExtracted()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 5
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 11
rs 9.6111
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\YaEtl\Traits\CsvHandlerTrait;
14
use fab2s\YaEtl\YaEtlException;
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, string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
35
    {
36
        parent::__construct($input);
37
        $this->delimiter = $delimiter;
38
        $this->enclosure = $enclosure;
39
        $this->escape    = $escape;
40
    }
41
42
    /**
43
     * @return iterable
44
     */
45
    protected function getExtracted(): iterable
46
    {
47
        if (!$this->readBom() || !$this->readSep() || false === ($firstRecord = $this->readHeader())) {
48
            return;
49
        }
50
51
        /* @var array $firstRecord */
52
        yield $this->bakeRecord($firstRecord);
53
        while (null !== ($record = $this->getNextNonEmptyRecord())) {
54
            /* @var array $record */
55
            yield $this->bakeRecord($record);
56
        }
57
    }
58
59
    /**
60
     * @param array $record
61
     *
62
     * @return array
63
     */
64
    protected function bakeRecord(array $record): array
65
    {
66
        return isset($this->header) ? array_combine($this->header, $record) : $record;
67
    }
68
69
    /**
70
     * @return array|null
71
     */
72
    protected function readHeader(): ?array
73
    {
74
        if (null === ($firstRecord = $this->getNextNonEmptyRecord())) {
75
            return null;
76
        }
77
78
        if ($this->useHeader) {
79
            $this->header = $this->header ?? array_map('trim', $firstRecord);
80
81
            return $this->getNextNonEmptyRecord();
82
        }
83
84
        return $firstRecord;
85
    }
86
87
    /**
88
     * @return bool
89
     */
90
    protected function readSep(): bool
91
    {
92
        if (null === ($firstChar = $this->getNextNonEmptyChars())) {
93
            return false;
94
        }
95
96
        $firstCharPos = ftell($this->handle);
97
        /* @var string $firstChar */
98
        if ($firstChar === 's') {
99
            if (false === ($chars = fread($this->handle, 4))) {
100
                return false;
101
            }
102
103
            /* @var string $chars */
104
            $line = $firstChar . $chars;
105
            if (strpos($line, 'sep=') === 0) {
106
                $this->useSep    = true;
107
                $this->delimiter = $line[4];
108
109
                return !fseek($this->handle, $firstCharPos + 5);
110
            }
111
        }
112
113
        return !fseek($this->handle, $firstCharPos - 1);
114
    }
115
116
    /**
117
     * @return array|null
118
     */
119
    protected function getNextNonEmptyRecord(): ? array
120
    {
121
        do {
122
            if (false === ($record = fgetcsv($this->handle, 0, $this->delimiter, $this->enclosure, $this->escape))) {
123
                return null;
124
            }
125
126
            if ($record === [null]) {
127
                // empty line
128
                continue;
129
            }
130
131
            return $record;
132
        } while (!feof($this->handle));
133
    }
134
}
135