Writer::write()   F
last analyzed

Complexity

Conditions 21
Paths > 20000

Size

Total Lines 190
Code Lines 104

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 462

Importance

Changes 4
Bugs 3 Features 0
Metric Value
cc 21
eloc 104
c 4
b 3
f 0
nc 30737
nop 1
dl 0
loc 190
ccs 0
cts 100
cp 0
crap 462
rs 0

How to fix   Long Method    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 SPSS\Sav;
4
5
use SPSS\Buffer;
6
use SPSS\Exception;
7
use SPSS\Sav\Record\Info;
8
use SPSS\Utils;
9
10
class Writer
11
{
12
    /**
13
     * @var Record\Header
14
     */
15
    public $header;
16
17
    /**
18
     * @var Record\Variable[]
19
     */
20
    public $variables = [];
21
22
    /**
23
     * @var Record\ValueLabel[]
24
     */
25
    public $valueLabels = [];
26
27
    /**
28
     * @var Record\Document
29
     */
30
    public $document;
31
32
    /**
33
     * @var InfoRecordSet
34
     */
35
    private $info = [];
36
37
    /**
38
     * @var Record\Data
39
     */
40
    public $data;
41
42
    /**
43
     * @var Buffer
44
     */
45
    protected $buffer;
46
47
    /**
48
     * Writer constructor.
49
     *
50
     * @param array $data
51
     * @throws \Exception
52
     */
53
    public function __construct($data = [])
54
    {
55
        $this->buffer = Buffer::factory();
56
        $this->buffer->context = $this;
57
58
        $this->info = new InfoRecordSet();
59
        if (! empty($data)) {
60
            $this->write($data);
61
        }
62
    }
63
64
    /**
65
     * @param array $data
66
     * @throws \Exception
67
     */
68
    public function write($data)
69
    {
70
        $this->header = new Record\Header($data['header']);
71
        $this->header->nominalCaseSize = 0;
72
        $this->header->casesCount = 0;
73
74
        $this->info->set($this->prepareInfoRecord(
75
            Record\Info\MachineInteger::class,
76
            $data
77
        ));
78
79
        $this->info->set($this->prepareInfoRecord(
80
            Record\Info\MachineFloatingPoint::class,
81
            $data
82
        ));
83
84
        $this->info->set(new Record\Info\VariableDisplayParam());
85
        $this->info->set(new Record\Info\LongVariableNames());
86
        $this->info->set(new Record\Info\VeryLongString());
87
        $this->info->set($this->prepareInfoRecord(
88
            Record\Info\ExtendedNumberOfCases::class,
89
            $data
90
        ));
91
        $this->info->set(new Record\Info\VariableAttributes());
92
        $this->info->set(new Record\Info\LongStringValueLabels());
93
        $this->info->set(new Record\Info\LongStringMissingValues());
94
        $this->info->set(new Record\Info\CharacterEncoding('UTF-8'));
95
96
        $this->data = new Record\Data();
97
98
        $nominalIdx = 0;
99
100
        /**
101
         * @var bool[string] The variable names used in this SPSS file
102
         */
103
        $variableNames = [];
104
        /** @var Variable $var */
105
        foreach (array_values($data['variables']) as $idx => $var) {
106
            if (!$var instanceof Variable) {
107
                throw new \InvalidArgumentException('Variables must be instance of ' . Variable::class);
108
            }
109
110
            $variable = new Record\Variable();
111
112
            /**
113
             * @see \SPSS\Sav\Record\Variable::getSegmentName()
114
             *
115
             * Variable names in the SPSS file should be unique. If they are not, SPSS will rename them.
116
             * If SPSS renames them and it happens to be a long string then the segments will no longer share
117
             * the required prefix in the name.
118
             */
119
            $name = strtoupper(substr($var->getName(), 0, 8));
120
121
            $counter = 0;
122
            /**
123
             * Using base convert we can encode 36^3 = 46656 variables with a common 5 character prefix in an 8
124
             * character variable name. This should suffice since the current variable limit of SPSS is 32767
125
             * variables.
126
             */
127
            while (isset($variableNames[$name])) {
128
                $name = strtoupper(substr($var->getName(), 0, 5) . base_convert($counter, 10, 36));
129
                $counter++;
130
            }
131
132
            $variableNames[$name] = true;
133
            $variable->name = $name;
134
135
            if ($var->format == Variable::FORMAT_TYPE_A) {
136
                $variable->width = $var->getWidth();
137
            } else {
138
                $variable->width = 0;
139
            }
140
141
            $variable->label = $var->label;
142
            $variable->print = [
143
                0,
144
                $var->format,
145
                min($var->getWidth(), 255),
146
                $var->decimals,
147
            ];
148
            $variable->write = [
149
                0,
150
                $var->format,
151
                min($var->getWidth(), 255),
152
                $var->decimals,
153
            ];
154
155
            // TODO: refactory
156
            $shortName = $variable->name;
157
            $longName = $var->getName();
158
159
            if ($var->attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $var->attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
160
                $this->info[Record\Info\VariableAttributes::SUBTYPE][$longName] = $var->attributes;
161
            }
162
163
            if ($var->missing) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $var->missing of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
164
                if ($var->getWidth() <= 8) {
165
                    if (count($var->missing) >= 3) {
166
                        $variable->missingValuesFormat = 3;
167
                    } elseif (count($var->missing) == 2) {
168
                        $variable->missingValuesFormat = -2;
169
                    } else {
170
                        $variable->missingValuesFormat = 1;
171
                    }
172
                    $variable->missingValues = $var->missing;
173
                } else {
174
                    $this->info[Record\Info\LongStringMissingValues::SUBTYPE][$shortName] = $var->missing;
175
                }
176
            }
177
178
            $this->variables[$idx] = $variable;
179
180
            if ($var->values) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $var->values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
181
                if ($variable->width > 8) {
182
                    $this->info[Record\Info\LongStringValueLabels::SUBTYPE][$longName] = [
183
                        'width' => $var->getWidth(),
184
                        'values' => $var->values,
185
                    ];
186
                } else {
187
                    $valueLabel = new Record\ValueLabel([
188
                        'variables' => $this->variables,
189
                    ]);
190
                    foreach ($var->values as $key => $value) {
191
                        $valueLabel->labels[] = [
192
                            'value' => $key,
193
                            'label' => $value,
194
                        ];
195
                        $valueLabel->indexes = [$nominalIdx + 1];
196
                    }
197
                    $this->valueLabels[] = $valueLabel;
198
                }
199
            }
200
201
            $this->info[Record\Info\LongVariableNames::SUBTYPE][$shortName] = $var->getName();
202
203
            if (Record\Variable::isVeryLong($var->getWidth())) {
204
                $this->info[Record\Info\VeryLongString::SUBTYPE][$shortName] = $var->getWidth();
205
            }
206
207
            $segmentCount = Utils::widthToSegments($var->getWidth());
208
            for ($i = 0; $i < $segmentCount; $i++) {
209
                $this->info[Record\Info\VariableDisplayParam::SUBTYPE][] = [
210
                    $var->getMeasure(),
211
                    $var->getColumns(),
212
                    $var->getAlignment(),
213
                ];
214
            }
215
216
            // TODO: refactory
217
            $dataCount = count($var->data);
218
            if ($dataCount > $this->header->casesCount) {
219
                $this->header->casesCount = $dataCount;
220
            }
221
222
            foreach ($var->data as $case => $value) {
223
                $this->data->matrix[$case][$idx] = $value;
224
            }
225
226
            $nominalIdx += $var->getOcts();
227
        }
228
229
        $this->header->nominalCaseSize = $nominalIdx;
230
231
        // write header
232
        $this->header->write($this->buffer);
233
234
        // write variables
235
        foreach ($this->variables as $variable) {
236
            $variable->write($this->buffer);
237
        }
238
239
        // write valueLabels
240
        foreach ($this->valueLabels as $valueLabel) {
241
            $valueLabel->write($this->buffer);
242
        }
243
244
        // write documents
245
        if (! empty($data['documents'])) {
246
            $this->document = new Record\Document([
247
                    'lines' => $data['documents'],
248
                ]
249
            );
250
            $this->document->write($this->buffer);
251
        }
252
253
        foreach ($this->info as $info) {
254
            $info->write($this->buffer);
255
        }
256
257
        $this->data->write($this->buffer);
258
    }
259
260
    /**
261
     * @param $file
262
     * @return false|int
263
     */
264
    public function save($file)
265
    {
266
        return $this->buffer->saveToFile($file);
267
    }
268
269
    /**
270
     * @return \SPSS\Buffer
271
     */
272
    public function getBuffer()
273
    {
274
        return $this->buffer;
275
    }
276
277
    /**
278
     * @param string $className
279
     * @param array $data
280
     * @param string $group
281
     * @return array
282
     * @throws Exception
283
     */
284
    private function prepareInfoRecord($className, $data, $group = 'info'): Info
285
    {
286
        if (! class_exists($className)) {
287
            throw new Exception('Unknown class');
288
        }
289
        $key = lcfirst(substr($className, strrpos($className, '\\') + 1));
290
291
        return new $className(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new $className(Is...group][$key] : array()) returns the type object which is incompatible with the documented return type array.
Loading history...
292
            isset($data[$group]) && isset($data[$group][$key]) ?
293
                $data[$group][$key] :
294
                []
295
        );
296
    }
297
}
298