Completed
Push — master ( bb5748...f73a2b )
by Andy
03:16
created

Writer::closeDocument()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 1
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->closeDocument();
22
    }
23
24
    /**
25
     * @inheritDoc
26
     */
27 1
    public function closeDocument()
28
    {
29 1
        $this->trimTrailingLineEnding();
30
31 1
        parent::closeDocument();
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
        $this->createDocument();
55
56
        $this->addRow($headers);
57
    }
58
59
    /**
60
     * Adds multiple rows of data to the CSV file.
61
     *
62
     * @param array $rows
63
     */
64
    public function addRows($rows)
65
    {
66
        foreach ($rows as $row) {
67
            $this->addRow($row);
68
        }
69
    }
70
71
    /**
72
     * Adds a row of data to the CSV file.
73
     *
74
     * @param array $row Row of data to add to the file.
75
     *
76
     * @return bool Whether the row was written to the file.
77
     */
78
    public function addRow($row)
79
    {
80
        if (!$this->getDocument()) {
81
            $this->createDocument();
82
        }
83
84
        $result = $this->getDocument()->fwrite($this->getCsvString($row));
85
86
        if ($result === null) {
87
            // @todo: handle error
88
            return false;
89
        }
90
91
        $this->bytesWritten += $result;
92
93
        return true;
94
    }
95
96
    /**
97
     * Returns the line ending delimiter used to separate rows
98
     * in the CSV file.
99
     *
100
     * @return string
101
     */
102
    public function getLineEnding()
103
    {
104
        return $this->lineEnding;
105
    }
106
107
    /**
108
     * Sets the line ending delimiter used to separate rows
109
     * in the CSV file.
110
     *
111
     * @param string $lineEnding
112
     *
113
     * @return Writer
114
     */
115
    public function setLineEnding($lineEnding)
116
    {
117
        $this->lineEnding = $lineEnding;
118
119
        return $this;
120
    }
121
122
    /**
123
     * Returns a string representation of a row to be written
124
     * as a line in a CSV file.
125
     *
126
     * @param array $row
127
     *
128
     * @return string
129
     */
130
    protected function getCsvString($row)
131
    {
132
        $result = $this->getEnclosure();
133
134
        $glue   = $this->getEnclosure() . $this->getDelimiter() . $this->getEnclosure();
135
        $result .= implode($glue, $this->escapeEnclosure($row));
136
137
        $result .= $this->getEnclosure();
138
        $result .= $this->getLineEnding();
139
140
        return $result;
141
    }
142
143
    /**
144
     * Trims the line ending delimiter from the end of the CSV file.
145
     * RFC-4180 states CSV files should not contain a trailing new line.
146
     */
147 1
    protected function trimTrailingLineEnding()
148
    {
149 1
        if ($this->bytesWritten > 0 && $this->getDocument()) {
150
            // Only trim the file if it ends with the line ending delimiter.
151
            $length = mb_strlen($this->getLineEnding());
152
153
            $this->getDocument()->fseek(-$length, SEEK_END);
154
            $chunk = $this->getDocument()->fread($length);
155
156
            if ($chunk === $this->getLineEnding()) {
157
                $this->getDocument()->ftruncate($this->bytesWritten - $length);
158
            }
159
        }
160 1
    }
161
162
    /**
163
     * Escapes the enclosure character recursively.
164
     * RFC-4180 states the enclosure character (usually double quotes) should be
165
     * escaped by itself, so " becomes "".
166
     *
167
     * @param mixed $data Array or string of data to escape.
168
     *
169
     * @return mixed Escaped data
170
     */
171
    protected function escapeEnclosure($data)
172
    {
173
        if (is_array($data)) {
174
            foreach ($data as $key => $value) {
175
                $data[$key] = $this->escapeEnclosure($value);
176
            }
177
        } else {
178
            $data = str_replace($this->getEnclosure(), str_repeat($this->getEnclosure(), 2), $data);
179
        }
180
181
        return $data;
182
    }
183
}
184