Completed
Push — master ( 3579b4...2422c3 )
by Kai
09:04
created

Parser::parseType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace Sassnowski\CsvSchema;
4
5
use League\Csv\Reader;
6
use Sassnowski\CsvSchema\Exceptions\CastException;
7
use Sassnowski\CsvSchema\Exceptions\UnsupportedTypeException;
8
9
/**
10
 * CSV Parser class. This is where the magic happens.
11
 *
12
 * @author K.Sassnowski <[email protected]>
13
 */
14
class Parser
15
{
16
    /**
17
     * @var array
18
     */
19
    private $config;
20
21
    /**
22
     * @var string
23
     */
24
    private $defaultDelimiter = ',';
25
26
    /**
27
     * @var string
28
     */
29
    private $defaultEnclosure = '"';
30
31
    /**
32
     * @var string
33
     */
34
    private $defaultEscape = '\\';
35
36
    /**
37
     * @var array
38
     */
39
    private static $customTypes = [];
40
41
    /**
42
     * Parser constructor.
43
     *
44
     * @param array $config
45
     */
46
    public function __construct(array $config)
47
    {
48
        $this->config = $config;
49
    }
50
51
    /**
52
     * Register a handler for a custom type. The handler will be called with
53
     * the value to parse.
54
     *
55
     * @param string   $type
56
     * @param callable $callback
57
     */
58
    public static function registerType($type, callable $callback)
59
    {
60
        static::$customTypes[$type] = $callback;
0 ignored issues
show
Bug introduced by
Since $customTypes is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $customTypes to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
61
    }
62
63
    /**
64
     * @param $input
65
     *
66
     * @return array
67
     */
68
    public function fromString($input)
69
    {
70
        return $this->parse(Reader::createFromString($input));
71
    }
72
73
    /**
74
     * @param string $filename
75
     *
76
     * @return array
77
     */
78
    public function fromFile($filename)
79
    {
80
        return $this->parse(Reader::createFromPath($filename));
81
    }
82
83
    /**
84
     * @param array $columns
85
     *
86
     * @return array
87
     */
88
    public function parseRow(array $columns)
89
    {
90
        return collect($columns)->zip($this->config['schema'])->flatMap(function ($pair, $index) {
91
            list($value, $type) = $pair;
92
93
            $parsed = $this->getValue($type, $value);
94
95
            $key = array_keys($this->config['schema'])[$index];
96
97
            return [$key => $parsed];
98
        })->all();
99
    }
100
101
    /**
102
     * @param Reader $reader
103
     *
104
     * @return array
105
     */
106
    protected function parse(Reader $reader)
107
    {
108
        $reader->setDelimiter($this->getConfigValue('delimiter', $this->defaultDelimiter));
109
        $reader->setEnclosure($this->getConfigValue('enclosure', $this->defaultEnclosure));
110
        $reader->setEscape($this->getConfigValue('escape', $this->defaultEscape));
111
112
        return collect($reader)->map(function ($row) {
113
            return (object) $this->parseRow($row);
114
        })->all();
115
    }
116
117
    /**
118
     * @param string $key
119
     * @param string $default
120
     *
121
     * @return mixed
122
     */
123
    protected function getConfigValue($key, $default)
124
    {
125
        return isset($this->config[$key]) ? $this->config[$key] : $default;
126
    }
127
128
    /**
129
     * @param string $type
130
     * @param string $value
131
     *
132
     * @return mixed
133
     *
134
     * @throws UnsupportedTypeException
135
     */
136
    protected function getValue($type, $value)
137
    {
138
        list($type, $parameters) = $this->parseType($type);
139
140
        if (method_exists($this, $this->getMethodName($type))) {
141
            $method = [$this, $this->getMethodName($type)];
142
        } elseif ($this->hasCustomType($type)) {
143
            $method = static::$customTypes[$type];
0 ignored issues
show
Bug introduced by
Since $customTypes is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $customTypes to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
144
        } else {
145
            throw new UnsupportedTypeException($type);
146
        }
147
148
        return call_user_func_array($method, [$value, $parameters]);
149
    }
150
151
    /**
152
     * @param string $type
153
     *
154
     * @return array
155
     */
156
    protected function parseType($type)
157
    {
158
        $parameters = [];
159
160
        if (strpos($type, ':') !== false) {
161
            list($type, $parameters) = explode(':', $type, 2);
162
        }
163
164
        return [$type, $parameters];
165
    }
166
167
    /**
168
     * @param string $type
169
     *
170
     * @return string
171
     */
172
    protected function getMethodName($type)
173
    {
174
        return 'parse'.ucfirst($type);
175
    }
176
177
    /**
178
     * @param string $value
179
     *
180
     * @return string
181
     */
182
    protected function parseString($value)
183
    {
184
        return (string) $value;
185
    }
186
187
    /**
188
     * @param string $value
189
     *
190
     * @return int
191
     *
192
     * @throws CastException
193
     */
194
    protected function parseInt($value)
195
    {
196
        $this->guardAgainstNonNumeric($value, 'int');
197
198
        return (int) $value;
199
    }
200
201
    /**
202
     * @param string $value
203
     *
204
     * @return float
205
     *
206
     * @throws CastException
207
     */
208
    protected function parseFloat($value)
209
    {
210
        $this->guardAgainstNonNumeric($value, 'float');
211
212
        return (float) $value;
213
    }
214
215
    /**
216
     * @param string $string
217
     * @param string $delimiter
218
     *
219
     * @return array
220
     */
221
    protected function parseArray($string, $delimiter)
222
    {
223
        return explode($delimiter, trim($string));
224
    }
225
226
    /**
227
     * @param string $value
228
     * @param string $targetType
229
     *
230
     * @throws CastException
231
     */
232
    protected function guardAgainstNonNumeric($value, $targetType)
233
    {
234
        if (!is_numeric($value)) {
235
            throw new CastException("Unable to cast value '$value' to type $targetType.");
236
        }
237
    }
238
239
    /**
240
     * @param string $type
241
     *
242
     * @return bool
243
     */
244
    protected function hasCustomType($type)
245
    {
246
        return isset(static::$customTypes[$type]);
0 ignored issues
show
Bug introduced by
Since $customTypes is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $customTypes to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
247
    }
248
}
249