Completed
Push — master ( be4243...1976df )
by Ori
19:42
created

Schema::__construct()   C

Complexity

Conditions 11
Paths 14

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 29
c 0
b 0
f 0
nc 14
nop 1
dl 0
loc 41
rs 5.2653

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace frictionlessdata\tableschema;
4
5
use frictionlessdata\tableschema\Fields\FieldsFactory;
6
7
/**
8
 *  Table Schema representation.
9
 *  Loads and validates a Table Schema descriptor from a descriptor / path to file / url containing the descriptor.
10
 */
11
class Schema
12
{
13
    protected $DEFAULT_FIELD_CLASS = '\\frictionlessdata\\tableschema\\Fields\\StringField';
14
15
    /**
16
     * Schema constructor.
17
     *
18
     * @param mixed $descriptor
19
     *
20
     * @throws Exceptions\SchemaLoadException
21
     * @throws Exceptions\SchemaValidationFailedException
22
     */
23
    public function __construct($descriptor = null)
24
    {
25
        if (is_null($descriptor)) {
26
            $this->descriptor = (object) ['fields' => []];
27
        } else {
28
            if (Utils::isJsonString($descriptor)) {
29
                // it's a json encoded string
30
                try {
31
                    $this->descriptor = json_decode($descriptor);
32
                } catch (\Exception $e) {
33
                    throw new Exceptions\SchemaLoadException($descriptor, null, $e->getMessage());
34
                }
35
                if (!$this->descriptor) {
36
                    throw new Exceptions\SchemaLoadException($descriptor, null, 'invalid json');
37
                }
38
            } elseif (is_string($descriptor)) {
39
                // it's a url or file path
40
                $descriptorSource = $descriptor;
41
                try {
42
                    $descriptor = file_get_contents($descriptorSource);
43
                } catch (\Exception $e) {
44
                    throw new Exceptions\SchemaLoadException(null, $descriptorSource, $e->getMessage());
45
                }
46
                try {
47
                    $this->descriptor = json_decode($descriptor);
48
                } catch (\Exception $e) {
49
                    throw new Exceptions\SchemaLoadException($descriptor, $descriptorSource, $e->getMessage());
50
                }
51
            } else {
52
                $this->descriptor = $descriptor;
53
            }
54
            if (!is_object($this->descriptor) && !is_array($this->descriptor)) {
55
                throw new Exceptions\SchemaLoadException($descriptor, null, 'descriptor must be an object or array');
56
            }
57
            $this->descriptor = json_decode(json_encode($this->descriptor));
58
            $validationErrors = SchemaValidator::validate($this->descriptor());
59
            if (count($validationErrors) > 0) {
60
                throw new Exceptions\SchemaValidationFailedException($validationErrors);
61
            }
62
        }
63
    }
64
65
    /**
66
     * loads and validates the given descriptor source (php object / string / path to file / url)
67
     * returns an array of validation error objects.
68
     *
69
     * @param mixed $descriptor
70
     *
71
     * @return array
72
     */
73
    public static function validate($descriptor)
74
    {
75
        try {
76
            new static($descriptor);
77
78
            return [];
79
        } catch (Exceptions\SchemaLoadException $e) {
80
            return [
81
                new SchemaValidationError(SchemaValidationError::LOAD_FAILED, $e->getMessage()),
82
            ];
83
        } catch (Exceptions\SchemaValidationFailedException $e) {
84
            return $e->validationErrors;
85
        }
86
    }
87
88
    /**
89
     * @return object
90
     */
91
    public function descriptor()
92
    {
93
        return $this->descriptor;
94
    }
95
96
    public function fullDescriptor()
97
    {
98
        $fullDescriptor = $this->descriptor();
99
        $fullFieldDescriptors = [];
100
        foreach ($this->fields() as $field) {
0 ignored issues
show
Bug introduced by
The expression $this->fields() of type array|this<frictionlessdata\tableschema\Schema> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
101
            $fullFieldDescriptors[] = $field->fullDescriptor();
102
        }
103
        $fullDescriptor->fields = $fullFieldDescriptors;
104
        $fullDescriptor->missingValues = $this->missingValues();
105
106
        return $fullDescriptor;
107
    }
108
109
    public function field($name, $field = null)
110
    {
111
        $fields = $this->fields();
112
        if (!is_null($field)) {
113
            $fields[$name] = $field;
114
115
            return $this->fields($fields);
116
        } elseif (array_key_exists($name, $fields)) {
117
            return $fields[$name];
118
        } else {
119
            throw new \Exception("unknown field name: {$name}");
120
        }
121
    }
122
123
    public function removeField($name)
124
    {
125
        $fields = $this->fields();
126
        unset($fields[$name]);
127
128
        return $this->fields($fields);
129
    }
130
131
    /**
132
     * @return Fields\BaseField[]|Schema array of field name => field object or the schema in case of editing
133
     */
134
    public function fields($newFields = null)
135
    {
136
        if (is_null($newFields)) {
137
            if (empty($this->fieldsCache)) {
138
                foreach ($this->descriptor()->fields as $fieldDescriptor) {
139
                    if (!array_key_exists('type', $fieldDescriptor)) {
140
                        $field = new $this->DEFAULT_FIELD_CLASS($fieldDescriptor);
141
                    } else {
142
                        $field = Fields\FieldsFactory::field($fieldDescriptor);
143
                    }
144
                    $this->fieldsCache[$field->name()] = $field;
145
                }
146
            }
147
148
            return $this->fieldsCache;
149
        } else {
150
            $this->descriptor()->fields = [];
151
            $this->fieldsCache = [];
152
            foreach ($newFields as $name => $field) {
153
                $field = FieldsFactory::field($field, $name);
154
                $this->fieldsCache[$name] = $field;
155
                $this->descriptor()->fields[] = $field->descriptor();
156
            }
157
158
            return $this->revalidate();
159
        }
160
    }
161
162
    public function missingValues($missingValues = null)
163
    {
164
        if (is_null($missingValues)) {
165
            return isset($this->descriptor()->missingValues) ? $this->descriptor()->missingValues : [''];
166
        } else {
167
            $this->descriptor()->missingValues = $missingValues;
168
169
            return $this->revalidate();
170
        }
171
    }
172
173
    public function primaryKey($primaryKey = null)
174
    {
175
        if (is_null($primaryKey)) {
176
            $primaryKey = isset($this->descriptor()->primaryKey) ? $this->descriptor()->primaryKey : [];
177
178
            return is_array($primaryKey) ? $primaryKey : [$primaryKey];
179
        } else {
180
            $this->descriptor()->primaryKey = $primaryKey;
181
182
            return $this->revalidate();
183
        }
184
    }
185
186
    public function foreignKeys($foreignKeys = null)
187
    {
188
        if (is_null($foreignKeys)) {
189
            $foreignKeys = isset($this->descriptor()->foreignKeys) ? $this->descriptor()->foreignKeys : [];
190
            foreach ($foreignKeys as &$foreignKey) {
191
                if (!is_array($foreignKey->fields)) {
192
                    $foreignKey->fields = [$foreignKey->fields];
193
                }
194
                if (!is_array($foreignKey->reference->fields)) {
195
                    $foreignKey->reference->fields = [$foreignKey->reference->fields];
196
                }
197
            }
198
199
            return $foreignKeys;
200
        } else {
201
            $this->descriptor()->foreignKeys = $foreignKeys;
202
203
            return $this->revalidate();
204
        }
205
    }
206
207
    /**
208
     * @param mixed[] $row
209
     *
210
     * @return mixed[]
211
     *
212
     * @throws Exceptions\FieldValidationException
213
     */
214
    public function castRow($row)
215
    {
216
        $outRow = [];
217
        $validationErrors = [];
218
        foreach ($this->fields() as $fieldName => $field) {
0 ignored issues
show
Bug introduced by
The expression $this->fields() of type array|this<frictionlessdata\tableschema\Schema> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
219
            $value = array_key_exists($fieldName, $row) ? $row[$fieldName] : null;
220
            if (in_array($value, $this->missingValues())) {
221
                $value = null;
222
            }
223
            try {
224
                $outRow[$fieldName] = $field->castValue($value);
225
            } catch (Exceptions\FieldValidationException $e) {
226
                $validationErrors = array_merge($validationErrors, $e->validationErrors);
227
            }
228
        }
229
        if (count($validationErrors) > 0) {
230
            throw new Exceptions\FieldValidationException($validationErrors);
231
        }
232
233
        return $outRow;
234
    }
235
236
    /**
237
     * @param array $row
238
     *
239
     * @return SchemaValidationError[]
240
     */
241
    public function validateRow($row)
242
    {
243
        try {
244
            $this->castRow($row);
245
246
            return [];
247
        } catch (Exceptions\FieldValidationException $e) {
248
            return $e->validationErrors;
249
        }
250
    }
251
252
    public function save($filename)
253
    {
254
        file_put_contents($filename, json_encode($this->fullDescriptor()));
255
    }
256
257
    public function revalidate()
258
    {
259
        $validationErrors = SchemaValidator::validate($this->descriptor());
260
        if (count($validationErrors) > 0) {
261
            throw new Exceptions\SchemaValidationFailedException($validationErrors);
262
        } else {
263
            return $this;
264
        }
265
    }
266
267
    protected $descriptor;
268
    protected $fieldsCache = [];
269
}
270