Writer   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 179
ccs 74
cts 74
cp 1
rs 10
c 0
b 0
f 0
wmc 24

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A addRows() 0 8 3
A setSeekableFlag() 0 9 2
A writeRows() 0 14 4
A updateNewLine() 0 5 3
A setHeader() 0 8 2
A closeStream() 0 4 1
A writeRow() 0 6 2
A addRow() 0 4 1
A writeToString() 0 5 1
A openStream() 0 13 3
A saveToFile() 0 5 1
1
<?php declare(strict_types=1);
2
3
/**
4
 * This file is part of the Csv-Machine package.
5
 *
6
 * (c) Dan McAdams <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace RoadBunch\Csv;
13
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\LogLevel;
16
use RoadBunch\Csv\Exception\FormatterException;
17
use RoadBunch\Csv\Exception\InvalidInputArrayException;
18
use RoadBunch\Csv\Header\Header;
19
use RoadBunch\Csv\Header\HeaderInterface;
20
21
/**
22
 * Class Writer
23
 *
24
 * @author  Dan McAdams
25
 * @package RoadBunch\Csv
26
 */
27
class Writer extends Csv implements WriterInterface
28
{
29
    /** @var HeaderInterface */
30
    protected $header;
31
32
    /** @var array */
33
    protected $rows = [];
34
35
    /** @var bool */
36
    protected $isStreamSeekable = false;
37
38
    /** @var resource */
39
    protected $handle;
40
41
    /**
42
     * Writer constructor.
43
     * @param LoggerInterface|null $logger
44
     */
45 7
    public function __construct(LoggerInterface $logger = null)
46
    {
47 7
        parent::__construct($logger);
48 7
        $this->logger->info(sprintf('New %s created', __CLASS__));
49 7
    }
50
51
    /**
52
     * @param array $header
53
     * @param mixed $formatters
54
     * @return void
55
     * @throws FormatterException
56
     * @throws InvalidInputArrayException
57
     */
58 5
    public function setHeader(array $header, array $formatters = [])
59
    {
60 5
        $header = new Header($header);
61 5
        foreach ($formatters as $formatter) {
62 1
            $header->addFormatter($formatter);
63
        }
64 5
        $this->header = $header;
65 5
        $this->logger->debug('Header row set');
66 5
    }
67
68
    /**
69
     * No row array passed will create an empty row
70
     *
71
     * @param  array $row
72
     * @return void
73
     */
74 4
    public function addRow(array $row = [])
75
    {
76 4
        $this->rows[] = $row;
77 4
        $this->logger->debug(sprintf('Row added with %d elements', count($row)));
78 4
    }
79
80
    /**
81
     * @param array $rows
82
     * @throws InvalidInputArrayException
83
     * @return void
84
     */
85 3
    public function addRows(array $rows)
86
    {
87 3
        foreach ($rows as $row) {
88 3
            if (!is_array($row)) {
89 1
                $this->logger->critical('Each row must be an array');
90 1
                throw new InvalidInputArrayException('Element must be an array');
91
            }
92 2
            $this->addRow($row);
93
        }
94 2
    }
95
96
    /**
97
     * @return string
98
     * @throws \Exception
99
     */
100 1
    public function writeToString(): string
101
    {
102 1
        ob_start();
103 1
        $this->saveToFile('php://output');
104 1
        return ob_get_clean();
105
    }
106
107
    /**
108
     * Write the CSV to the stream
109
     *
110
     * @param string $filename
111
     * @throws \Exception
112
     */
113 6
    public function saveToFile(string $filename)
114
    {
115 6
        $this->openStream($filename);
116 5
        $this->writeRows();
117 5
        $this->closeStream();
118 5
    }
119
120
    /**
121
     * @param string $filename
122
     * @throws \Exception
123
     */
124 6
    private function openStream(string $filename)
125
    {
126 6
        $this->handle = fopen($filename, 'w+');
127
128 6
        if (false === $this->handle) {
129 1
            $message = !empty(error_get_last()['message']) ? error_get_last()['message'] : '';
130 1
            $message = sprintf('Could not open file for writing. %s', $message);
131
132 1
            $this->logger->critical($message);
133 1
            throw new \Exception($message);
134
        }
135 5
        $this->logger->info(sprintf('Opening stream %s', $filename));
136 5
        $this->setSeekableFlag();
137 5
    }
138
139
    /**
140
     * Write CSV header and rows to stream
141
     */
142 5
    private function writeRows()
143
    {
144 5
        if (!empty($this->header)) {
145 5
            $this->writeRow($this->header->getFormattedColumns());
146 5
            $this->logger->info('Header row written');
147
        }
148 5
        $rowCount = 0;
149 5
        foreach ($this->rows as $row) {
150 5
            $this->writeRow($row);
151 5
            $rowCount++;
152
        }
153 5
        $this->logger->log(
154 5
            $level = (0 === $rowCount) ? LogLevel::WARNING : LogLevel::INFO,
155 5
            sprintf('%d rows written', $rowCount)
156
        );
157 5
    }
158
159
    /**
160
     * Write a row to the stream, if needed, update line endings
161
     *
162
     * @param $row
163
     */
164 5
    private function writeRow(array $row)
165
    {
166 5
        fputcsv($this->handle, $row, $this->delimiter, $this->enclosure, $this->escape);
167
168 5
        if ($this->isStreamSeekable) {
169 3
            $this->updateNewLine();
170
        }
171 5
    }
172
173
    /**
174
     * If the requested newline is not PHP default \n update the newline character
175
     */
176 3
    private function updateNewLine()
177
    {
178 3
        if ((Newline::NEWLINE_LF !== $this->newline) && (0 === fseek($this->handle, -1, SEEK_CUR))) {
179 1
            fwrite($this->handle, $this->newline);
180 1
            $this->logger->debug(sprintf('Update newline character %s', addslashes($this->newline)));
181
        }
182 3
    }
183
184
    /**
185
     * Close the stream
186
     */
187 5
    private function closeStream()
188
    {
189 5
        fclose($this->handle);
190 5
        $this->logger->info('Stream closed');
191 5
    }
192
193
    /**
194
     * Checks resource, if it's seekable set the seekable flag
195
     * this will be used when determining if line endings should be/can be updated
196
     */
197 5
    private function setSeekableFlag()
198
    {
199 5
        if (stream_get_meta_data($this->handle)['seekable']) {
200 3
            $this->isStreamSeekable = true;
201 3
            $this->logger->debug('Stream is seekable');
202 3
            return;
203
        }
204 2
        $this->isStreamSeekable = false;
205 2
        $this->logger->debug('Stream is not seekable');
206 2
    }
207
}
208