Completed
Push — master ( 5c1aea...9ef1c4 )
by Luke
03:03
created

Flavor::assertValidAttribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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