Flavor   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 236
ccs 25
cts 25
cp 1
rs 10
c 0
b 0
f 0
wmc 11
lcom 1
cbo 1

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 4
A __get() 0 6 1
A __set() 0 4 1
A hasHeader() 0 4 1
A copy() 0 4 1
A toArray() 0 4 1
A assertValidAttribute() 0 6 2
1
<?php
2
3
/*
4
 * CSVelte: Slender, elegant CSV for PHP
5
 * Inspired by Python's CSV module and Frictionless Data and the W3C's CSV
6
 * standardization efforts, CSVelte was written in an effort to take all the
7
 * suck out of working with CSV.
8
 *
9
 * @version   {version}
10
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
11
 * @author    Luke Visinoni <[email protected]>
12
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
13
 */
14
namespace CSVelte;
15
16
use CSVelte\Exception\ImmutableException;
17
use InvalidArgumentException;
18
19
/**
20
 * CSV Flavor.
21
 *
22
 * Represents a particular "flavor"  of CSV. Inspired by python's csv "dialects".
23
 * Also inspired by Frictionless Data's "dialect description" format and the W3C's
24
 * CSV on the Web Working Group and their work on CSV dialects.
25
 *
26
 * @package CSVelte
27
 * @subpackage Flavor
28
 *
29
 * @since v0.1
30
 *
31
 * @property string $delimiter The delimiter character
32
 * @property string $quoteChar The quoting character
33
 * @property string $lineTerminator The character sequence used to terminate rows of data
34
 * @property string $escapeChar The character used to escape quotes within a quoted string
35
 *     Mutually exclusive to $doubleQuote
36
 * @property bool $doubleQuote If true, quote characters will be escaped by preceding them
37
 *     with another quote character. Mutually exclusive to $escapeChar
38
 * @property string $quoteStyle One of four class constants that determine which cells are quoted
39
 * @property bool $header If true, first row should be treated as a header row
40
 */
41
class Flavor
42
{
43
    /**
44
     * Quote all cells.
45
     * Set Flavor::$quoteStyle to this to quote all cells, regardless of data type.
46
     *
47
     * @var string
48
     */
49
    const QUOTE_ALL = 'quote_all';
50
51
    /**
52
     * Quote no cells.
53
     * Set Flavor::$quoteStyle to this to quote no columns, regardless of data type.
54
     *
55
     * @var string
56
     */
57
    const QUOTE_NONE = 'quote_none';
58
59
    /**
60
     * Quote minimal columns.
61
     * Set Flavor::$quoteStyle to this to quote only cells that contain special
62
     * characters such as newlines or the delimiter character.
63
     *
64
     * @var string
65
     */
66
    const QUOTE_MINIMAL = 'quote_minimal';
67
68
    /**
69
     * Quote non-numeric cells.
70
     * Set Flavor::$quoteStyle to this to quote only cells that contain
71
     * non-numeric data.
72
     *
73
     * @var string
74
     */
75
    const QUOTE_NONNUMERIC = 'quote_nonnumeric';
76
77
    /**
78
     * Delimiter character.
79
     * This is the character that will be used to separate data cells within a
80
     * row of CSV data. Usually a comma.
81
     *
82
     * @var string
83
     */
84
    protected $delimiter = ',';
85
86
    /**
87
     * Quote character.
88
     * This is the character that will be used to enclose (quote) data cells. It
89
     * is usually a double quote character but single quote is allowed.
90
     *
91
     * @var string
92
     */
93
    protected $quoteChar = '"';
94
95
    /**
96
     * Escape character.
97
     * This character will be used to escape quotes within quoted text. It is
98
     * mutually exclusive to the doubleQuote attribute. Usually a backspace.
99
     *
100
     * @var string
101
     */
102
    protected $escapeChar = '\\';
103
104
    /**
105
     * Double quote escape mode.
106
     * If set to true, quote characters within quoted text will be escaped by
107
     * preceding them with the same quote character.
108
     *
109
     * @var bool
110
     */
111
    protected $doubleQuote = true;
112
113
    /**
114
     * Not yet implemented.
115
     *
116
     * @ignore
117
     */
118
    // protected $skipInitialSpace = false;
119
120
    /**
121
     * Quoting style.
122
     * This may be set to one of four values:
123
     *     * *Flavor::QUOTE_NONE* - To never quote data cells
124
     *     * *Flavor::QUOTE_ALL* - To always quote data cells
125
     *     * *Flavor::QUOTE_MINIMAL* - To only quote data cells that contain special characters such as quote character or delimiter character
126
     *     * *Flavor::QUOTE_NONNUMERIC* - To quote data cells that contain non-numeric data.
127
     *
128
     * @var string
129
     */
130
    protected $quoteStyle = self::QUOTE_MINIMAL;
131
132
    /**
133
     * Line terminator string sequence.
134
     * This is a character or sequence of characters that will be used to denote
135
     * the end of a row within the data.
136
     *
137
     * @var string
138
     */
139
    protected $lineTerminator = "\r\n";
140
141
    /**
142
     * Header.
143
     * If set to true, this means the first line of the CSV data is to be treated
144
     * as the column headers.
145
     *
146
     * @var bool
147
     */
148
    protected $header;
149
150
    /**
151
     * Class constructor.
152
     *
153
     * The attributes that make up a flavor object can only be specified by
154
     * passing them in an array as key => value pairs to the constructor. Once
155
     * the flavor object is created, its attributes cannot be changed.
156
     *
157
     * @param array $attributes The attributes that define this particular flavor. These
158
     *                          attributes are immutable. They can only be set here.
159
     */
160 49
    public function __construct($attributes = null)
161
    {
162 49
        if (!is_null($attributes)) {
163 37
            if (!is_array($attributes)) {
164
                // @todo throw exception?
165 1
                return;
166
            }
167 36
            foreach ($attributes as $attr => $val) {
168 36
                $this->assertValidAttribute($attr);
169 36
                $this->$attr = $val;
170 36
            }
171 36
        }
172 49
    }
173
174
    /**
175
     * Attribute accessor magic method.
176
     *
177
     * @param string $attr The attribute to "get"
178
     *
179
     * @throws InvalidArgumentException
180
     *
181
     * @return string The attribute value
182
     *
183
     * @internal
184
     */
185 38
    public function __get($attr)
186
    {
187 38
        $this->assertValidAttribute($attr);
188
189 37
        return $this->$attr;
190
    }
191
192
    /**
193
     * Attribute accessor (setter) magic method.
194
     * Disabled because attributes are immutable (read-only).
195
     *
196
     * @param string $attr The attribute name you're attempting to set
197
     * @param mixed  $val  The attribute value
198
     *
199
     * @throws ImmutableException
200
     *
201
     * @internal param The $string attribute to "set"
202
     * @internal param The $string attribute value
203
     * @internal
204
     */
205 1
    public function __set($attr, $val)
206
    {
207 1
        throw new ImmutableException('Cannot change attributes on an immutable object: ' . self::class . '::$' . $attr);
208
    }
209
210
    /**
211
     * Does this flavor of CSV have a header row?
212
     *
213
     * The difference between $flavor->header and $flavor->hasHeader() is that
214
     * hasHeader() is always going to give you a boolean value, whereas
215
     * $flavor->header may be null. A null value for header could mean that the
216
     * taster class could not reliably determine whether or not there was a
217
     * header row or it could simply mean that the flavor was instantiated with
218
     * no value for the header property.
219
     *
220
     * @return bool
221
     */
222 1
    public function hasHeader()
223
    {
224 1
        return (bool) $this->header;
225
    }
226
227
    /**
228
     * Copy this flavor object.
229
     *
230
     * Because flavor attributes are immutable, it is implossible to change their
231
     * attributes. If you need to change a flavor's attributes, call this method
232
     * instead, specifying which attributes are to be changed.
233
     *
234
     * @param array $attribs An array of attribute name/values to change in the copied flavor
235
     *
236
     * @return Flavor A flavor object with your new attributes
237
     *
238
     * @todo I may want to remove the array type-hint so that this can accept
239
     *     array-like objects and iterables as well. Not sure...
240
     */
241 6
    public function copy(array $attribs = [])
242
    {
243 6
        return new self(array_merge($this->toArray(), $attribs));
244
    }
245
246
    /**
247
     * Get this object as an array.
248
     *
249
     * @return array This object as an array
250
     */
251 9
    public function toArray()
252
    {
253 9
        return get_object_vars($this);
254
    }
255
256
    /**
257
     * Assert valid attribute name.
258
     * Assert that a particular attribute is valid (basically just that it exists)
259
     * and throw an exception otherwise.
260
     *
261
     * @param string $attr The attribute to check validity of
262
     *
263
     * @throws InvalidArgumentException
264
     *
265
     * @internal
266
     *
267
     * @todo This should accept a second parameter for value that asserts the value
268
     *     is a valid value
269
     */
270 45
    protected function assertValidAttribute($attr)
271
    {
272 45
        if (!property_exists(self::class, $attr)) {
273 1
            throw new InvalidArgumentException('Unknown attribute: ' . $attr);
274
        }
275 44
    }
276
}
277