Completed
Push — master ( 4b020b...461219 )
by Harry
03:33
created

CsvFormatter::format()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 21
ccs 15
cts 15
cp 1
rs 8.7624
cc 5
eloc 13
nc 6
nop 1
crap 5
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
22
class CsvFormatter implements FormatterInterface
23
{
24
    use RowProcessor;
25
    use InvokeFormatter;
26
27
    /** @var CsvFormatInterface */
28
    private $csvFormat;
29
    /** @var string[] */
30
    private $escapeChars;
31
    /** @var string[] */
32
    private $replaceChars;
33
    /** @var string */
34
    private $initial;
35
    /** @var bool */
36
    private $first = true;
37
38
    /**
39
     * @param CsvFormatInterface $csvFormat
40
     */
41 21
    public function __construct(CsvFormatInterface $csvFormat)
42
    {
43 21
        $this->csvFormat = $csvFormat;
44
45 21
        $this->buildReplacements();
46
47 21
        $this->initial = (!is_null($this->csvFormat->getBom())) ? $this->csvFormat->getBom() : '';
48
49 21
        $this->addProcessor(new DateTimeProcessor());
50 21
        $this->addProcessor(new BoolProcessor());
51 21
        $this->addProcessor(new ObjectToStringProcessor());
52 21
    }
53
54
    /**
55
     * Build replacements to perform for each entry
56
     */
57 21
    private function buildReplacements()
58
    {
59 21
        if ($this->csvFormat->getEscape()) {
60 17
            $this->escapeChars = [
61 17
                $this->csvFormat->getEscape(), // escape escape first so that it doesn't re-escape later on
62 17
                $this->csvFormat->getDelimiter(),
63 17
                "\n",
64 17
                "\r",
65 17
                "\t",
66
            ];
67 17
            if ($this->csvFormat->hasQuote() && !$this->csvFormat->useDoubleQuotes()) {
68 14
                $this->escapeChars[] = $this->csvFormat->getQuote();
69 14
            }
70
71 17
            $this->escapeChars = array_unique($this->escapeChars);
72
73 17
            $this->replaceChars = array_map(function ($char) {
74 17
                return $this->csvFormat->getEscape() . $char;
75 17
            }, $this->escapeChars);
76 17
        }
77
78 21
        if ($this->csvFormat->hasQuote() && $this->csvFormat->useDoubleQuotes()) {
79 2
            $this->escapeChars[] = $this->csvFormat->getQuote();
80 2
            $this->replaceChars[] = str_repeat($this->csvFormat->getQuote(), 2);
81 2
        }
82 21
    }
83
84
    /**
85
     * @param array $data
86
     *
87
     * @return string
88
     */
89 14
    public function format(array $data)
90
    {
91 14
        $prefix = '';
92 14
        if ($this->first && $this->csvFormat->hasHeaderRow()) {
93 3
            $this->first = false;
94 3
            $postHeaderPad = $this->csvFormat->getDataStart() - $this->csvFormat->getHeaderRow();
95 3
            $prefix = $this->format(array_keys($data)) . str_repeat($this->getRowSeparator(), $postHeaderPad);
96 3
        }
97
98 14
        $data = $this->process($data);
99
100 14
        foreach ($data as &$element) {
101 14
            if (is_null($element)) {
102 4
                $element = $this->csvFormat->getNullValue();
103 4
            } else {
104 14
                $element = $this->csvFormat->getQuote() . $this->escape($element) . $this->csvFormat->getQuote();
105
            }
106 14
        }
107
108 14
        return $prefix . $this->encode(implode($this->csvFormat->getDelimiter(), $data));
109
    }
110
111
    /**
112
     * @param string $string
113
     *
114
     * @return string
115
     */
116 14
    protected function escape($string)
117
    {
118 14
        return str_replace($this->escapeChars, $this->replaceChars, $string);
119
    }
120
121
    /**
122
     * @param string $string
123
     *
124
     * @return string
125
     */
126 18
    private function encode($string)
127
    {
128 18
        return mb_convert_encoding($string, $this->csvFormat->getEncoding());
129
    }
130
131
    /**
132
     * Return an initial block if required
133
     *
134
     * @return string
135
     */
136 6
    public function getInitialBlock()
137
    {
138 6
        $linePad = $this->csvFormat->hasHeaderRow() ?
139 6
            $this->csvFormat->getHeaderRow() - 1 :
140 6
            $this->csvFormat->getDataStart() - 1;
141
142 6
        return $this->initial . str_repeat($this->getRowSeparator(), $linePad);
143
    }
144
145
    /**
146
     * Get a separator between each row
147
     *
148
     * @return string
149
     */
150 10
    public function getRowSeparator()
151
    {
152 10
        return $this->encode($this->csvFormat->getNewLine());
153
    }
154
155
    /**
156
     * Return a closing block if required
157
     *
158
     * @return string
159
     */
160 6
    public function getClosingBlock()
161
    {
162 6
        return '';
163
    }
164
}
165