CsvFormatter::format()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
nop 1
crap 3
1
<?php
2
/**
3
 * This file is part of graze/data-file
4
 *
5
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/data-file/blob/master/LICENSE.md
11
 * @link    https://github.com/graze/data-file
12
 */
13
14
namespace Graze\DataFile\Format\Formatter;
15
16
use Graze\DataFile\Format\CsvFormatInterface;
17
use Graze\DataFile\Format\Processor\BoolProcessor;
18
use Graze\DataFile\Format\Processor\DateTimeProcessor;
19
use Graze\DataFile\Format\Processor\ObjectToStringProcessor;
20
use Graze\DataFile\Format\Processor\RowProcessor;
21
use Traversable;
22
23
class CsvFormatter implements FormatterInterface
24
{
25
    use RowProcessor;
26
    use InvokeFormatter;
27
    use TraversableTrait;
28
29
    /** @var CsvFormatInterface */
30
    private $csvFormat;
31
    /** @var string[] */
32
    private $escapeChars;
33
    /** @var string[] */
34
    private $replaceChars;
35
    /** @var string */
36
    private $initial;
37
    /** @var bool */
38
    private $first = true;
39
40
    /**
41
     * @param CsvFormatInterface $csvFormat
42
     */
43 22
    public function __construct(CsvFormatInterface $csvFormat)
44
    {
45 22
        $this->csvFormat = $csvFormat;
46
47 22
        $this->buildReplacements();
48
49 22
        $this->initial = (!is_null($this->csvFormat->getBom())) ? $this->csvFormat->getBom() : '';
50
51 22
        $this->addProcessor(new DateTimeProcessor());
52 22
        $this->addProcessor(new BoolProcessor());
53 22
        $this->addProcessor(new ObjectToStringProcessor());
54 22
    }
55
56
    /**
57
     * Build replacements to perform for each entry
58
     */
59 22
    private function buildReplacements()
60
    {
61 22
        if ($this->csvFormat->getEscape()) {
62 18
            $this->escapeChars = [
63 18
                $this->csvFormat->getEscape(), // escape escape first so that it doesn't re-escape later on
64 18
                $this->csvFormat->getDelimiter(),
65 18
                "\n",
66 18
                "\r",
67 18
                "\t",
68
            ];
69 18
            if ($this->csvFormat->hasQuote() && !$this->csvFormat->useDoubleQuotes()) {
70 15
                $this->escapeChars[] = $this->csvFormat->getQuote();
71
            }
72
73 18
            $this->escapeChars = array_unique($this->escapeChars);
74
75 18
            $this->replaceChars = array_map(function ($char) {
76 18
                return $this->csvFormat->getEscape() . $char;
77 18
            }, $this->escapeChars);
78
        }
79
80 22
        if ($this->csvFormat->hasQuote() && $this->csvFormat->useDoubleQuotes()) {
81 2
            $this->escapeChars[] = $this->csvFormat->getQuote();
82 2
            $this->replaceChars[] = str_repeat($this->csvFormat->getQuote(), 2);
83
        }
84 22
    }
85
86
    /**
87
     * Gets a prefix for headers if required
88
     *
89
     * @param array $data
90
     *
91
     * @return string
92
     */
93 14
    private function getHeaderPrefix(array $data)
94
    {
95 14
        if ($this->first && $this->csvFormat->hasHeaderRow()) {
96 3
            $this->first = false;
97 3
            $postHeaderPad = $this->csvFormat->getDataStart() - $this->csvFormat->getHeaderRow();
98 3
            return $this->format(array_keys($data)) . str_repeat($this->getRowSeparator(), $postHeaderPad);
99
        }
100 14
        return '';
101
    }
102
103
    /**
104
     * @param array|Traversable $row
105
     *
106
     * @return string
107
     */
108 15
    public function format($row)
109
    {
110 15
        $data = $this->getArray($row);
111 14
        $prefix = $this->getHeaderPrefix($data);
112 14
        $data = $this->process($data);
113
114 14
        foreach ($data as &$element) {
115 14
            if (is_null($element)) {
116 4
                $element = $this->csvFormat->getNullValue();
117
            } else {
118 14
                $element = $this->csvFormat->getQuote() . $this->escape($element) . $this->csvFormat->getQuote();
119
            }
120
        }
121
122 14
        return $prefix . $this->encode(implode($this->csvFormat->getDelimiter(), $data));
123
    }
124
125
    /**
126
     * @param string $string
127
     *
128
     * @return string
129
     */
130 14
    protected function escape($string)
131
    {
132 14
        return str_replace($this->escapeChars, $this->replaceChars, $string);
133
    }
134
135
    /**
136
     * @param string $string
137
     *
138
     * @return string
139
     */
140 18
    private function encode($string)
141
    {
142 18
        return mb_convert_encoding($string, $this->csvFormat->getEncoding());
143
    }
144
145
    /**
146
     * Return an initial block if required
147
     *
148
     * @return string
149
     */
150 6
    public function getInitialBlock()
151
    {
152 6
        $linePad = $this->csvFormat->hasHeaderRow() ?
153 1
            $this->csvFormat->getHeaderRow() - 1 :
154 6
            $this->csvFormat->getDataStart() - 1;
155
156 6
        return $this->initial . str_repeat($this->getRowSeparator(), $linePad);
157
    }
158
159
    /**
160
     * Get a separator between each row
161
     *
162
     * @return string
163
     */
164 10
    public function getRowSeparator()
165
    {
166 10
        return $this->encode($this->csvFormat->getNewLine());
167
    }
168
169
    /**
170
     * Return a closing block if required
171
     *
172
     * @return string
173
     */
174 6
    public function getClosingBlock()
175
    {
176 6
        return '';
177
    }
178
}
179