Completed
Push — master ( 48e73f...b60bd4 )
by
unknown
28s queued 13s
created

BaseResource::handlesProfile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace frictionlessdata\datapackage\Resources;
4
5
use frictionlessdata\datapackage\DataStreams\BaseDataStream;
6
use frictionlessdata\datapackage\Registry;
7
use frictionlessdata\datapackage\Validators\ResourceValidationError;
8
use frictionlessdata\datapackage\Validators\ResourceValidator;
9
use frictionlessdata\datapackage\Exceptions\ResourceValidationFailedException;
10
use frictionlessdata\datapackage\Utils;
11
12
abstract class BaseResource implements \Iterator
13
{
14
15
    /**
16
     * BaseResource constructor.
17
     *
18
     * @param object $descriptor
19
     * @param null|string $basePath
20
     *
21
     * @param bool $skipValidations
22
     *
23
     * @throws \frictionlessdata\datapackage\Exceptions\ResourceValidationFailedException
24
     */
25
    public function __construct($descriptor, $basePath, $skipValidations = false)
26
    {
27
        $this->basePath = $basePath;
28
        $this->descriptor = Utils::objectify($descriptor);
29
        $this->skipValidations = $skipValidations;
30
        if (!$this->skipValidations) {
31
            $validationErrors = $this->validateResource();
32
            if (count($validationErrors) > 0) {
33
                throw new ResourceValidationFailedException($validationErrors);
34
            }
35
        }
36
    }
37
38
    public static function handlesDescriptor($descriptor)
39
    {
40
        return static::handlesProfile(Registry::getResourceValidationProfile($descriptor));
41
    }
42
43
    public function read($readOptions = null)
44
    {
45
        $limit = ($readOptions && isset($readOptions['limit'])) ? $readOptions['limit'] : null;
46
        $rows = [];
47
        foreach ($this->dataStreams() as $dataStream) {
0 ignored issues
show
Bug introduced by
The expression $this->dataStreams() of type array<integer,object<fri...s\BaseDataStream>>|null 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...
48
            if (isset($dataStream->table)) {
49
                $readOptions['limit'] = $limit;
50
                foreach ($dataStream->table->read($readOptions) as $row) {
51
                    $rows[] = $row;
52
                    if ($limit !== null) {
53
                        --$limit;
54
                        if ($limit < 0) {
55
                            break;
56
                        }
57
                    }
58
                }
59
            } else {
60
                foreach ($dataStream as $row) {
61
                    $rows[] = $row;
62
                    if ($limit !== null) {
63
                        --$limit;
64
                        if ($limit < 0) {
65
                            break;
66
                        }
67
                    }
68
                }
69
            }
70
            if ($limit !== null && $limit < 0) {
71
                break;
72
            }
73
        }
74
75
        return $rows;
76
    }
77
78
    /**
79
     * Loads $this->dataStreams based on $this->path() and $this->data
80
     * @return BaseDataStream[]|null
81
     */
82
    public function dataStreams()
83
    {
84
        if (is_null($this->dataStreams)) {
85
            $this->dataStreams = [];
86
            foreach ($this->path() as $path) {
87
                $this->dataStreams[] = $this->getDataStream($path);
88
            }
89
            $data = $this->data();
90
            if ($data) {
91
                $this->dataStreams[] = $this->getInlineDataStream($data);
92
            }
93
        }
94
95
        return $this->dataStreams;
96
    }
97
98
    /**
99
     * @return object
100
     */
101
    public function descriptor()
102
    {
103
        return $this->descriptor;
104
    }
105
106
    /**
107
     * @return string
108
     */
109
    public function name()
110
    {
111
        return $this->descriptor()->name;
112
    }
113
114
    public function path()
115
    {
116
        if (isset($this->descriptor()->path)) {
117
            $path = $this->descriptor()->path;
118
            if (!is_array($path)) {
119
                $path = [$path];
120
            }
121
122
            return $path;
123
        } else {
124
            return [];
125
        }
126
    }
127
128
    /**
129
     * If the resource's $path is local (non-http)
130
     * @return bool
131
     */
132
    public function isLocal()
133
    {
134
        return !$this->isRemote();
135
    }
136
137
    /**
138
     * If the resource's $path is remote (http)
139
     * @return bool
140
     */
141
    public function isRemote()
142
    {
143
        $path = $this->path();
144
        return Utils::isHttpSource(is_array($path) && count($path) > 0 ? $path[0] : $path);
145
    }
146
147
    public function data()
148
    {
149
        return isset($this->descriptor()->data) ? $this->descriptor()->data : null;
150
    }
151
152
    // standard iterator functions - to iterate over the data sources
153
    public function rewind()
154
    {
155
        $this->dataStreams = null;
156
        $this->currentDataStream = 0;
157
        foreach ($this->dataStreams() as $dataStream) {
0 ignored issues
show
Bug introduced by
The expression $this->dataStreams() of type array<integer,object<fri...s\BaseDataStream>>|null 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...
158
            $dataStream->rewind();
159
        }
160
    }
161
162
    public function current()
163
    {
164
        return $this->dataStreams()[$this->currentDataStream]->current();
165
    }
166
167
    public function key()
168
    {
169
        return $this->dataStreams()[$this->currentDataStream]->key();
170
    }
171
172
    public function next()
173
    {
174
        return $this->dataStreams()[$this->currentDataStream]->next();
175
    }
176
177
    public function valid()
178
    {
179
        $dataStreams = $this->dataStreams();
180
        if ($dataStreams[$this->currentDataStream]->valid()) {
181
            // current data stream is still valid
182
            return true;
183
        } else {
184
            ++$this->currentDataStream;
185
            if (isset($dataStreams[$this->currentDataStream])) {
186
                // current data stream is done, but we have another data stream
187
                return true;
188
            } else {
189
                // no more data and no more data streams
190
                return false;
191
            }
192
        }
193
    }
194
195
    public function getFileExtension()
196
    {
197
        return '';
198
    }
199
200
    public function save($baseFilename)
201
    {
202
        $dataStreams = $this->dataStreams();
203
        $numDataStreams = count($dataStreams);
204
        $fileNames = [];
205
        $i = 0;
206
        foreach ($dataStreams as $dataStream) {
0 ignored issues
show
Bug introduced by
The expression $dataStreams of type array<integer,object<fri...s\BaseDataStream>>|null 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...
207
            if ($numDataStreams == 1) {
208
                $filename = $baseFilename.$this->getFileExtension();
209
            } else {
210
                $filename = $baseFilename.'-data-'.$i.$this->getFileExtension();
211
            }
212
            $fileNames[] = $filename;
213
            $dataStream->save($filename);
214
            ++$i;
215
        }
216
217
        return $fileNames;
218
    }
219
220
    public static function validateDataSource($dataSource, $basePath = null)
221
    {
222
        $errors = [];
223
        $dataSource = static::normalizeDataSource($dataSource, $basePath);
224
        if (!Utils::isHttpSource($dataSource) && !file_exists($dataSource)) {
225
            $errors[] = new ResourceValidationError(
226
                ResourceValidationError::SCHEMA_VIOLATION,
227
                "data source file does not exist or is not readable: {$dataSource}"
228
            );
229
        }
230
231
        return $errors;
232
    }
233
234
    /**
235
     * allows extending classes to add custom sources
236
     * used by unit tests to add a mock http source.
237
     *
238
     * @param string $dataSource
239
     * @param string|null $basePath
240
     *
241
     * @return string
242
     */
243
    public static function normalizeDataSource($dataSource, string $basePath = null)
244
    {
245
        if (!empty($basePath) && !Utils::isHttpSource($dataSource)) {
246
            // TODO: support JSON pointers
247
            $absPath = $basePath.DIRECTORY_SEPARATOR.$dataSource;
248
            if (file_exists($absPath)) {
249
                $dataSource = $absPath;
250
            }
251
        }
252
253
        return $dataSource;
254
    }
255
256
    protected $descriptor;
257
    protected $basePath;
258
    protected $skipValidations = false;
259
    protected $currentDataPosition = 0;
260
    protected $currentDataStream = 0;
261
    protected $dataStreams = null;
262
263
    protected function validateResource()
264
    {
265
        return ResourceValidator::validate($this->descriptor(), $this->basePath);
0 ignored issues
show
Bug introduced by
It seems like $this->basePath can also be of type string; however, frictionlessdata\datapac...seValidator::validate() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
266
    }
267
268
    /**
269
     * @param string $dataSource
270
     *
271
     * @return BaseDataStream
272
     */
273
    abstract protected function getDataStream($dataSource);
274
275
    abstract protected function getInlineDataStream($data);
276
277
    protected static function handlesProfile($profile)
278
    {
279
        return false;
280
    }
281
}
282