Passed
Branch master (9671df)
by Andy
02:10
created

Writer::write()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 2
1
<?php
2
3
namespace Palmtree\Csv;
4
5
/**
6
 * Writes an array to a CSV file.
7
 */
8
class Writer extends AbstractCsv
9
{
10
    /** @var string */
11
    protected $fopenMode = 'w+';
12
    /** @var string */
13
    protected $lineEnding = "\r\n";
14
    /** @var int */
15
    protected $bytesWritten = 0;
16
17
    public static function write($file, $data)
18
    {
19
        $writer = new static($file);
20
        $writer->setData($data);
21
        $writer->closeFileHandle();
22
    }
23
24
    /**
25
     * @inheritDoc
26
     */
27 1
    public function closeFileHandle()
28
    {
29 1
        $this->trimTrailingLineEnding();
30
31 1
        parent::closeFileHandle();
32 1
    }
33
34
    /**
35
     * Sets headers and all rows on the CSV file and
36
     * then closes the file handle.
37
     *
38
     * @param $data
39
     */
40
    public function setData($data)
41
    {
42
        if ($this->hasHeaders()) {
43
            $this->setHeaders(array_keys(reset($data)));
44
        }
45
46
        $this->addRows($data);
47
    }
48
49
    /**
50
     * @param array $headers
51
     */
52
    public function setHeaders(array $headers)
53
    {
54
        // We're setting headers so start again with a new file handle.
55
        $this->createFileHandle();
56
57
        $this->addRow($headers);
58
    }
59
60
    /**
61
     * Adds multiple rows of data to the CSV file.
62
     *
63
     * @param array $rows
64
     */
65
    public function addRows($rows)
66
    {
67
        foreach ($rows as $row) {
68
            $this->addRow($row);
69
        }
70
    }
71
72
    /**
73
     * Adds a row of data to the CSV file.
74
     *
75
     * @param array $row Row of data to add to the file.
76
     *
77
     * @return bool Whether the row was written to the file.
78
     */
79
    public function addRow($row)
80
    {
81
        if (!$this->getFileHandle()) {
82
            $this->createFileHandle();
83
        }
84
85
        $result = fwrite($this->getFileHandle(), $this->getCsvString($row));
86
87
        if ($result === false) {
88
            // @todo: handle error
89
            return false;
90
        }
91
92
        $this->bytesWritten += $result;
93
94
        return true;
95
    }
96
97
    /**
98
     * Returns the line ending delimiter used to separate rows
99
     * in the CSV file.
100
     *
101
     * @return string
102
     */
103
    public function getLineEnding()
104
    {
105
        return $this->lineEnding;
106
    }
107
108
    /**
109
     * Sets the line ending delimiter used to separate rows
110
     * in the CSV file.
111
     *
112
     * @param string $lineEnding
113
     *
114
     * @return Writer
115
     */
116
    public function setLineEnding($lineEnding)
117
    {
118
        $this->lineEnding = $lineEnding;
119
120
        return $this;
121
    }
122
123
    /**
124
     * Returns a string representation of a row to be written
125
     * as a line in a CSV file.
126
     *
127
     * @param array $row
128
     *
129
     * @return string
130
     */
131
    protected function getCsvString($row)
132
    {
133
        $result = $this->getEnclosure();
134
135
        $glue   = $this->getEnclosure() . $this->getDelimiter() . $this->getEnclosure();
136
        $result .= implode($glue, $this->escapeEnclosure($row));
137
138
        $result .= $this->getEnclosure();
139
        $result .= $this->getLineEnding();
140
141
        return $result;
142
    }
143
144
    /**
145
     * Trims the line ending delimiter from the end of the CSV file.
146
     * RFC-4180 states CSV files should not contain a trailing new line.
147
     */
148 1
    protected function trimTrailingLineEnding()
149
    {
150 1
        if ($this->bytesWritten > 0 && $this->getFileHandle()) {
151
            // Only trim the file if it ends with the line ending delimiter.
152
            $length = mb_strlen($this->getLineEnding());
153
154
            fseek($this->getFileHandle(), -$length, SEEK_END);
155
            $chunk = fread($this->getFileHandle(), $length);
156
157
            if ($chunk === $this->getLineEnding()) {
158
                ftruncate($this->getFileHandle(), $this->bytesWritten - $length);
159
            }
160
        }
161 1
    }
162
163
    /**
164
     * Escapes the enclosure character recursively.
165
     * RFC-4180 states the enclosure character (usually double quotes) should be
166
     * escaped by itself, so " becomes "".
167
     *
168
     * @param mixed $data Array or string of data to escape.
169
     *
170
     * @return mixed Escaped data
171
     */
172
    protected function escapeEnclosure($data)
173
    {
174
        if (is_array($data)) {
175
            foreach ($data as $key => $value) {
176
                $data[$key] = $this->escapeEnclosure($value);
177
            }
178
        } else {
179
            $data = str_replace($this->getEnclosure(), str_repeat($this->getEnclosure(), 2), $data);
180
        }
181
182
        return $data;
183
    }
184
}
185