Completed
Pull Request — master (#8)
by Harry
03:33
created

CsvFormat::hasQuote()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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;
15
16
use Graze\CsvToken\Csv\Bom;
17
use Graze\DataFile\Helper\GetOptionTrait;
18
19
class CsvFormat implements CsvFormatInterface
20
{
21
    use GetOptionTrait;
22
23
    const DEFAULT_DELIMITER    = ',';
24
    const DEFAULT_NULL         = '\\N';
25
    const DEFAULT_HEADER_ROW   = -1;
26
    const DEFAULT_DATA_START   = 1;
27
    const DEFAULT_QUOTE        = '"';
28
    const DEFAULT_ESCAPE       = '\\';
29
    const DEFAULT_LIMIT        = -1;
30
    const DEFAULT_DOUBLE_QUOTE = false;
31
    const DEFAULT_ENCODING     = 'UTF-8';
32
    const DEFAULT_NEW_LINE     = "\n";
33
    const DEFAULT_BOM          = null;
34
35
    const OPTION_DELIMITER    = 'delimiter';
36
    const OPTION_NULL         = 'null';
37
    const OPTION_HEADER_ROW   = 'headerRow';
38
    const OPTION_DATA_START   = 'dataStart';
39
    const OPTION_NEW_LINE     = 'newLine';
40
    const OPTION_QUOTE        = 'quote';
41
    const OPTION_ESCAPE       = 'escape';
42
    const OPTION_LIMIT        = 'limit';
43
    const OPTION_DOUBLE_QUOTE = 'doubleQuote';
44
    const OPTION_BOM          = 'bom';
45
    const OPTION_ENCODING     = 'encoding';
46
47
    /** @var string */
48
    protected $delimiter;
49
    /** @var string */
50
    protected $quote;
51
    /** @var string */
52
    protected $nullValue;
53
    /** @var int */
54
    protected $headerRow;
55
    /** @var string */
56
    protected $newLines;
57
    /** @var string */
58
    protected $escape;
59
    /** @var int */
60
    protected $limit;
61
    /** @var bool */
62
    protected $doubleQuote;
63
    /** @var int */
64
    protected $dataStart;
65
    /** @var string */
66
    protected $boms;
67
    /** @var string */
68
    protected $encoding;
69
70
    /**
71
     * @param array $options -delimiter <string> (Default: ,) Character to use between fields
72
     *                       -quoteCharacter <string> (Default: ")
73
     *                       -nullOutput <string> (Default: \N)
74
     *                       -headerRow <int> (Default: -1) -1 for no header row. (1 is the first line of the file)
75
     *                       -dataStart <int> (Default: 1) The line where the data starts (1 is the first list of the
76
     *                       file)
77
     *                       -lineTerminator <array> (Default: ["\n","\r","\r\n"])
78
     *                       -escape <string> (Default: \\) Character to use for escaping
79
     *                       -limit <int> Total number of data rows to return
80
     *                       -doubleQuote <bool> instances of quote in fields are indicated by a double quote
81
     *                       -bom <array> (Default: BOM_ALL) Specify a ByteOrderMark for this file (see Bom::BOM_*)
82
     *                       -encoding <string> (Default: UTF-8) Specify the encoding of the csv file
83
     */
84 16
    public function __construct(array $options = [])
85
    {
86 16
        $this->options = $options;
87 16
        $this->delimiter = $this->getOption(static::OPTION_DELIMITER, static::DEFAULT_DELIMITER);
88 16
        $this->quote = $this->getOption(static::OPTION_QUOTE, static::DEFAULT_QUOTE);
89 16
        $this->nullValue = $this->getOption(static::OPTION_NULL, static::DEFAULT_NULL);
90 16
        $this->headerRow = $this->getOption(static::OPTION_HEADER_ROW, static::DEFAULT_HEADER_ROW);
91 16
        $this->dataStart = $this->getOption(static::OPTION_DATA_START, static::DEFAULT_DATA_START);
92 16
        $this->escape = $this->getOption(static::OPTION_ESCAPE, static::DEFAULT_ESCAPE);
93 16
        $this->limit = $this->getOption(static::OPTION_LIMIT, static::DEFAULT_LIMIT);
94 16
        $this->doubleQuote = $this->getOption(static::OPTION_DOUBLE_QUOTE, static::DEFAULT_DOUBLE_QUOTE);
95 16
        $this->encoding = $this->getOption(static::OPTION_ENCODING, static::DEFAULT_ENCODING);
96 16
        $this->setBom($this->getOption(static::OPTION_BOM, static::DEFAULT_BOM));
97 16
        $this->setNewLine($this->getOption(static::OPTION_NEW_LINE, ["\n", "\r", "\r\n"]));
98 16
    }
99
100
    /**
101
     * @return string
102
     */
103 27
    public function getDelimiter()
104
    {
105 27
        return $this->delimiter;
106
    }
107
108
    /**
109
     * @param string $delimiter
110
     *
111
     * @return static
112
     */
113 2
    public function setDelimiter($delimiter)
114
    {
115 2
        $this->delimiter = $delimiter;
116 2
        return $this;
117
    }
118
119
    /**
120
     * @return string
121
     */
122 26
    public function getQuote()
123
    {
124 26
        return $this->quote;
125
    }
126
127
    /**
128
     * @return bool
129
     */
130 20
    public function hasQuote()
131
    {
132 20
        return $this->quote <> '';
133
    }
134
135
    /**
136
     * @param string $quote
137
     *
138
     * @return static
139
     */
140 1
    public function setQuote($quote)
141
    {
142 1
        $this->quote = $quote;
143 1
        return $this;
144
    }
145
146
    /**
147
     * @return string
148
     */
149 16
    public function getNullValue()
150
    {
151 16
        return $this->nullValue;
152
    }
153
154
    /**
155
     * @param string $nullValue
156
     *
157
     * @return static
158
     */
159 1
    public function setNullValue($nullValue)
160
    {
161 1
        $this->nullValue = $nullValue;
162 1
        return $this;
163
    }
164
165
    /**
166
     * @return bool
167
     */
168 13
    public function hasHeaderRow()
169
    {
170 13
        return $this->headerRow > 0;
171
    }
172
173
    /**
174
     * @param int $headerRow
175
     *
176
     * @return static
177
     */
178 2
    public function setHeaderRow($headerRow)
179
    {
180 2
        $this->headerRow = $headerRow;
181 2
        return $this;
182
    }
183
184
    /**
185
     * @return int
186
     */
187 7
    public function getHeaderRow()
188
    {
189 7
        return $this->headerRow;
190
    }
191
192
    /**
193
     * @return int
194
     */
195 13
    public function getDataStart()
196
    {
197 13
        if ($this->hasHeaderRow() && $this->getHeaderRow() >= $this->dataStart) {
198 4
            return max(1, $this->getHeaderRow() + 1);
199
        }
200 10
        return max(1, $this->dataStart);
201
    }
202
203
    /**
204
     * @param int $row
205
     *
206
     * @return static
207
     */
208 2
    public function setDataStart($row)
209
    {
210 2
        $this->dataStart = $row;
211 2
        return $this;
212
    }
213
214
    /**
215
     * Type type of file format (defined in FileFormatType::)
216
     *
217
     * @return string
218
     */
219 7
    public function getType()
220
    {
221 7
        return 'csv';
222
    }
223
224
    /**
225
     * @return string
226
     */
227 29
    public function getEscape()
228
    {
229 29
        return $this->escape;
230
    }
231
232
    /**
233
     * @param string $escape
234
     *
235
     * @return static
236
     */
237 1
    public function setEscape($escape)
238
    {
239 1
        $this->escape = $escape;
240 1
        return $this;
241
    }
242
243
    /**
244
     * @return bool
245
     */
246 3
    public function hasEscape()
247
    {
248 3
        return $this->escape !== '';
249
    }
250
251
    /**
252
     * Get the limit that should be returned (-1 for no limit)
253
     *
254
     * @return int
255
     */
256 12
    public function getLimit()
257
    {
258 12
        return $this->limit;
259
    }
260
261
    /**
262
     * Set the limit of the number of items to be returned (-1 for not limit)
263
     *
264
     * @param int $limit
265
     *
266
     * @return static
267
     */
268 1
    public function setLimit($limit)
269
    {
270 1
        $this->limit = $limit;
271 1
        return $this;
272
    }
273
274
    /**
275
     * @return bool
276
     */
277 24
    public function useDoubleQuotes()
278
    {
279 24
        return $this->doubleQuote;
280
    }
281
282
    /**
283
     * @param bool $doubleQuote
284
     *
285
     * @return static
286
     */
287 1
    public function setDoubleQuote($doubleQuote)
288
    {
289 1
        $this->doubleQuote = $doubleQuote;
290 1
        return $this;
291
    }
292
293
    /**
294
     * @param null|string[]|string $bom
295
     *
296
     * @return static
297
     */
298 16
    public function setBom($bom)
299
    {
300 16
        $this->boms = $bom;
0 ignored issues
show
Documentation Bug introduced by
It seems like $bom can also be of type array<integer,string>. However, the property $boms is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
301 16
        if (!is_null($bom) && !is_array($bom)) {
302 4
            Bom::getEncoding($bom);
303 3
        }
304 16
        return $this;
305
    }
306
307
    /**
308
     * @return string
309
     */
310 24
    public function getEncoding()
311
    {
312 24
        if (!is_null($this->boms)) {
313 3
            $bom = is_array($this->boms) ? reset($this->boms) : $this->boms;
314 3
            return Bom::getEncoding($bom);
315
        }
316 22
        return $this->encoding;
317
    }
318
319
    /**
320
     * @param string $encoding
321
     *
322
     * @return static
323
     */
324 1
    public function setEncoding($encoding)
325
    {
326 1
        $this->encoding = $encoding;
327 1
        return $this;
328
    }
329
330
    /**
331
     * @return string[]
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
332
     */
333 12
    public function getNewLines()
334
    {
335 12
        return $this->newLines;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->newLines; (string) is incompatible with the return type declared by the interface Graze\DataFile\Format\Cs...tInterface::getNewLines of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
336
    }
337
338
    /**
339
     * @param string|string[] $newLine
340
     *
341
     * @return static
342
     */
343 16
    public function setNewLine($newLine)
344
    {
345 16
        $this->newLines = is_array($newLine) ? $newLine : [$newLine];
0 ignored issues
show
Documentation Bug introduced by
It seems like is_array($newLine) ? $newLine : array($newLine) of type array is incompatible with the declared type string of property $newLines.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
346 16
        return $this;
347
    }
348
349
    /**
350
     * @return string[]
351
     */
352 12
    public function getBoms()
353
    {
354 12
        if (is_null($this->boms)) {
355 10
            return $this->getDefaultBoms();
356 3
        } elseif (is_array($this->boms)) {
357 1
            return $this->boms;
358
        } else {
359 3
            return [$this->boms];
360
        }
361
    }
362
363
    /**
364
     * @return string[]
365
     */
366 10
    private function getDefaultBoms()
367
    {
368 10
        return [Bom::BOM_UTF8, Bom::BOM_UTF16_BE, Bom::BOM_UTF16_LE, Bom::BOM_UTF32_BE, Bom::BOM_UTF32_LE];
369
    }
370
371
    /**
372
     * Get a new line for writing
373
     *
374
     * @return string
375
     */
376 7
    public function getNewLine()
377
    {
378 7
        return is_array($this->newLines) ? reset($this->newLines) : $this->newLines;
379
    }
380
381
    /**
382
     * Get a ByteOrderMark for writing if applicable
383
     *
384
     * @return string|null
385
     */
386 20
    public function getBom()
387
    {
388 20
        return is_array($this->boms) ? reset($this->boms) : $this->boms;
389
    }
390
}
391