Passed
Push — master ( 386efc...fcbe67 )
by Sam
02:35
created

Writer::getBuffer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
namespace SPSS\Sav;
4
5
use SPSS\Buffer;
6
use SPSS\Exception;
7
use SPSS\Utils;
8
9
class Writer
10
{
11
    /**
12
     * @var Record\Header
13
     */
14
    public $header;
15
16
    /**
17
     * @var Record\Variable[]
18
     */
19
    public $variables = [];
20
21
    /**
22
     * @var Record\ValueLabel[]
23
     */
24
    public $valueLabels = [];
25
26
    /**
27
     * @var Record\Document
28
     */
29
    public $document;
30
31
    /**
32
     * @var InfoRecordSet
33
     */
34
    private $info = [];
35
36
    /**
37
     * @var Record\Data
38
     */
39
    public $data;
40
41
    /**
42
     * @var Buffer
43
     */
44
    protected $buffer;
45
46
    /**
47
     * Writer constructor.
48
     *
49
     * @param array $data
50
     * @throws \Exception
51
     */
52
    public function __construct($data = [])
53
    {
54
        $this->buffer = Buffer::factory();
55
        $this->buffer->context = $this;
56
57
        $this->info = new InfoRecordSet();
58
        if (! empty($data)) {
59
            $this->write($data);
60
        }
61
    }
62
63
    /**
64
     * @param array $data
65
     * @throws \Exception
66
     */
67
    public function write($data)
68
    {
69
        $this->header = new Record\Header($data['header']);
70
        $this->header->nominalCaseSize = 0;
71
        $this->header->casesCount = 0;
72
73
        $this->info[Record\Info\MachineInteger::SUBTYPE] = $this->prepareInfoRecord(
74
            Record\Info\MachineInteger::class,
75
            $data
76
        );
77
78
        $this->info[Record\Info\MachineFloatingPoint::SUBTYPE] = $this->prepareInfoRecord(
79
            Record\Info\MachineFloatingPoint::class,
80
            $data
81
        );
82
83
        $this->info[Record\Info\VariableDisplayParam::SUBTYPE] = new Record\Info\VariableDisplayParam();
84
        $this->info[Record\Info\LongVariableNames::SUBTYPE] = new Record\Info\LongVariableNames();
85
        $this->info[Record\Info\VeryLongString::SUBTYPE] = new Record\Info\VeryLongString();
86
        $this->info[Record\Info\ExtendedNumberOfCases::SUBTYPE] = $this->prepareInfoRecord(
87
            Record\Info\ExtendedNumberOfCases::class,
88
            $data
89
        );
90
        $this->info[Record\Info\VariableAttributes::SUBTYPE] = new Record\Info\VariableAttributes();
91
        $this->info[Record\Info\LongStringValueLabels::SUBTYPE] = new Record\Info\LongStringValueLabels();
92
        $this->info[Record\Info\LongStringMissingValues::SUBTYPE] = new Record\Info\LongStringMissingValues();
93
        $this->info[Record\Info\CharacterEncoding::SUBTYPE] = new Record\Info\CharacterEncoding('UTF-8');
94
95
        $this->data = new Record\Data();
96
97
        $nominalIdx = 0;
98
99
        /**
100
         * @var bool[string] The variable names used in this SPSS file
101
         */
102
        $variableNames = [];
103
        /** @var Variable $var */
104
        foreach (array_values($data['variables']) as $idx => $var) {
105
            if (!$var instanceof Variable) {
106
                throw new \InvalidArgumentException('Variables must be instance of ' . Variable::class);
107
            }
108
109
            $variable = new Record\Variable();
110
111
            /**
112
             * @see \SPSS\Sav\Record\Variable::getSegmentName()
113
             *
114
             * Variable names in the SPSS file should be unique. If they are not, SPSS will rename them.
115
             * If SPSS renames them and it happens to be a long string then the segments will no longer share
116
             * the required prefix in the name.
117
             */
118
            $name = strtoupper(substr($var->getName(), 0, 8));
119
120
            $counter = 0;
121
            /**
122
             * Using base convert we can encode 36^3 = 46656 variables with a common 5 character prefix in an 8
123
             * character variable name. This should suffice since the current variable limit of SPSS is 32767
124
             * variables.
125
             */
126
            while (isset($variableNames[$name])) {
127
                $name = strtoupper(substr($var->getName(), 0, 5) . base_convert($counter, 10, 36));
128
                $counter++;
129
            }
130
131
            $variableNames[$name] = true;
132
            $variable->name = $name;
133
134
            if ($var->format == Variable::FORMAT_TYPE_A) {
135
                $variable->width = $var->getWidth();
136
            } else {
137
                $variable->width = 0;
138
            }
139
140
            $variable->label = $var->label;
141
            $variable->print = [
142
                0,
143
                $var->format,
144
                min($var->getWidth(), 255),
145
                $var->decimals,
146
            ];
147
            $variable->write = [
148
                0,
149
                $var->format,
150
                min($var->getWidth(), 255),
151
                $var->decimals,
152
            ];
153
154
            // TODO: refactory
155
            $shortName = $variable->name;
156
            $longName = $var->getName();
157
158
            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...
159
                $this->info[Record\Info\VariableAttributes::SUBTYPE][$longName] = $var->attributes;
160
            }
161
162
            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...
163
                if ($var->getWidth() <= 8) {
164
                    if (count($var->missing) >= 3) {
165
                        $variable->missingValuesFormat = 3;
166
                    } elseif (count($var->missing) == 2) {
167
                        $variable->missingValuesFormat = -2;
168
                    } else {
169
                        $variable->missingValuesFormat = 1;
170
                    }
171
                    $variable->missingValues = $var->missing;
172
                } else {
173
                    $this->info[Record\Info\LongStringMissingValues::SUBTYPE][$shortName] = $var->missing;
174
                }
175
            }
176
177
            $this->variables[$idx] = $variable;
178
179
            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...
180
                if ($variable->width > 8) {
181
                    $this->info[Record\Info\LongStringValueLabels::SUBTYPE][$longName] = [
182
                        'width' => $var->getWidth(),
183
                        'values' => $var->values,
184
                    ];
185
                } else {
186
                    $valueLabel = new Record\ValueLabel([
187
                        'variables' => $this->variables,
188
                    ]);
189
                    foreach ($var->values as $key => $value) {
190
                        $valueLabel->labels[] = [
191
                            'value' => $key,
192
                            'label' => $value,
193
                        ];
194
                        $valueLabel->indexes = [$nominalIdx + 1];
195
                    }
196
                    $this->valueLabels[] = $valueLabel;
197
                }
198
            }
199
200
            $this->info[Record\Info\LongVariableNames::SUBTYPE][$shortName] = $var->getName();
201
202
            if (Record\Variable::isVeryLong($var->getWidth())) {
203
                $this->info[Record\Info\VeryLongString::SUBTYPE][$shortName] = $var->getWidth();
204
            }
205
206
            $segmentCount = Utils::widthToSegments($var->getWidth());
207
            for ($i = 0; $i < $segmentCount; $i++) {
208
                $this->info[Record\Info\VariableDisplayParam::SUBTYPE][] = [
209
                    $var->getMeasure(),
210
                    $var->getColumns(),
211
                    $var->getAlignment(),
212
                ];
213
            }
214
215
            // TODO: refactory
216
            $dataCount = count($var->data);
217
            if ($dataCount > $this->header->casesCount) {
218
                $this->header->casesCount = $dataCount;
219
            }
220
221
            foreach ($var->data as $case => $value) {
222
                $this->data->matrix[$case][$idx] = $value;
223
            }
224
225
            $nominalIdx += $var->getOcts();
226
        }
227
228
        $this->header->nominalCaseSize = $nominalIdx;
229
230
        // write header
231
        $this->header->write($this->buffer);
232
233
        // write variables
234
        foreach ($this->variables as $variable) {
235
            $variable->write($this->buffer);
236
        }
237
238
        // write valueLabels
239
        foreach ($this->valueLabels as $valueLabel) {
240
            $valueLabel->write($this->buffer);
241
        }
242
243
        // write documents
244
        if (! empty($data['documents'])) {
245
            $this->document = new Record\Document([
246
                    'lines' => $data['documents'],
247
                ]
248
            );
249
            $this->document->write($this->buffer);
250
        }
251
252
        foreach ($this->info as $info) {
253
            $info->write($this->buffer);
254
        }
255
256
        $this->data->write($this->buffer);
257
    }
258
259
    /**
260
     * @param $file
261
     * @return false|int
262
     */
263
    public function save($file)
264
    {
265
        return $this->buffer->saveToFile($file);
266
    }
267
268
    /**
269
     * @return \SPSS\Buffer
270
     */
271
    public function getBuffer()
272
    {
273
        return $this->buffer;
274
    }
275
276
    /**
277
     * @param string $className
278
     * @param array $data
279
     * @param string $group
280
     * @return array
281
     * @throws Exception
282
     */
283
    private function prepareInfoRecord($className, $data, $group = 'info')
284
    {
285
        if (! class_exists($className)) {
286
            throw new Exception('Unknown class');
287
        }
288
        $key = lcfirst(substr($className, strrpos($className, '\\') + 1));
289
290
        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...
291
            isset($data[$group]) && isset($data[$group][$key]) ?
292
                $data[$group][$key] :
293
                []
294
        );
295
    }
296
}
297