Passed
Pull Request — master (#218)
by Yuji
02:37
created

SupportVectorMachine::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 28
nc 1
nop 12

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\SupportVectorMachine;
6
7
use Phpml\Exception\InvalidArgumentException;
8
use Phpml\Exception\InvalidOperationException;
9
use Phpml\Exception\LibsvmCommandException;
10
use Phpml\Helper\Trainable;
11
12
class SupportVectorMachine
13
{
14
    use Trainable;
15
16
    /**
17
     * @var int
18
     */
19
    private $type;
20
21
    /**
22
     * @var int
23
     */
24
    private $kernel;
25
26
    /**
27
     * @var float
28
     */
29
    private $cost;
30
31
    /**
32
     * @var float
33
     */
34
    private $nu;
35
36
    /**
37
     * @var int
38
     */
39
    private $degree;
40
41
    /**
42
     * @var float|null
43
     */
44
    private $gamma;
45
46
    /**
47
     * @var float
48
     */
49
    private $coef0;
50
51
    /**
52
     * @var float
53
     */
54
    private $epsilon;
55
56
    /**
57
     * @var float
58
     */
59
    private $tolerance;
60
61
    /**
62
     * @var int
63
     */
64
    private $cacheSize;
65
66
    /**
67
     * @var bool
68
     */
69
    private $shrinking;
70
71
    /**
72
     * @var bool
73
     */
74
    private $probabilityEstimates;
75
76
    /**
77
     * @var string
78
     */
79
    private $binPath;
80
81
    /**
82
     * @var string
83
     */
84
    private $varPath;
85
86
    /**
87
     * @var string
88
     */
89
    private $model;
90
91
    /**
92
     * @var array
93
     */
94
    private $targets = [];
95
96
    public function __construct(
97
        int $type,
98
        int $kernel,
99
        float $cost = 1.0,
100
        float $nu = 0.5,
101
        int $degree = 3,
102
        ?float $gamma = null,
103
        float $coef0 = 0.0,
104
        float $epsilon = 0.1,
105
        float $tolerance = 0.001,
106
        int $cacheSize = 100,
107
        bool $shrinking = true,
108
        bool $probabilityEstimates = false
109
    ) {
110
        $this->type = $type;
111
        $this->kernel = $kernel;
112
        $this->cost = $cost;
113
        $this->nu = $nu;
114
        $this->degree = $degree;
115
        $this->gamma = $gamma;
116
        $this->coef0 = $coef0;
117
        $this->epsilon = $epsilon;
118
        $this->tolerance = $tolerance;
119
        $this->cacheSize = $cacheSize;
120
        $this->shrinking = $shrinking;
121
        $this->probabilityEstimates = $probabilityEstimates;
122
123
        $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR;
124
125
        $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR;
126
        $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR;
127
    }
128
129
    public function setBinPath(string $binPath): void
130
    {
131
        $this->ensureDirectorySeparator($binPath);
132
        $this->verifyBinPath($binPath);
133
134
        $this->binPath = $binPath;
135
    }
136
137
    public function setVarPath(string $varPath): void
138
    {
139
        if (!is_writable($varPath)) {
140
            throw InvalidArgumentException::pathNotWritable($varPath);
141
        }
142
143
        $this->ensureDirectorySeparator($varPath);
144
        $this->varPath = $varPath;
145
    }
146
147
    public function train(array $samples, array $targets): void
148
    {
149
        $this->samples = array_merge($this->samples, $samples);
150
        $this->targets = array_merge($this->targets, $targets);
151
152
        $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR]));
153
        file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet);
154
        $modelFileName = $trainingSetFileName.'-model';
155
156
        $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName);
157
        $output = [];
158
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
159
160
        unlink($trainingSetFileName);
161
162
        if ($return !== 0) {
163
            throw LibsvmCommandException::failedToRun($command, array_pop($output));
0 ignored issues
show
Documentation introduced by
array_pop($output) is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
164
        }
165
166
        $this->model = file_get_contents($modelFileName);
167
168
        unlink($modelFileName);
169
    }
170
171
    public function getModel(): string
172
    {
173
        return $this->model;
174
    }
175
176
    /**
177
     * @return array|string
178
     *
179
     * @throws LibsvmCommandException
180
     */
181
    public function predict(array $samples)
182
    {
183
        $predictions = $this->runSvmPredict($samples, false);
184
185
        if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
186
            $predictions = DataTransformer::predictions($predictions, $this->targets);
187
        } else {
188
            $predictions = explode(PHP_EOL, trim($predictions));
189
        }
190
191
        if (!is_array($samples[0])) {
192
            return $predictions[0];
193
        }
194
195
        return $predictions;
196
    }
197
198
    /**
199
     * @return array|string
200
     *
201
     * @throws LibsvmCommandException
202
     */
203
    public function predictProbability(array $samples)
204
    {
205
        if (!$this->probabilityEstimates) {
206
            throw new InvalidOperationException('Model does not support probabiliy estimates');
207
        }
208
209
        $predictions = $this->runSvmPredict($samples, true);
210
211
        if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
212
            $predictions = DataTransformer::probabilities($predictions, $this->targets);
213
        } else {
214
            $predictions = explode(PHP_EOL, trim($predictions));
215
        }
216
217
        if (!is_array($samples[0])) {
218
            return $predictions[0];
219
        }
220
221
        return $predictions;
222
    }
223
224
    private function runSvmPredict(array $samples, bool $probabilityEstimates): string
225
    {
226
        $testSet = DataTransformer::testSet($samples);
227
        file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet);
228
        file_put_contents($modelFileName = $testSetFileName.'-model', $this->model);
229
        $outputFileName = $testSetFileName.'-output';
230
231
        $command = $this->buildPredictCommand(
232
            $testSetFileName,
233
            $modelFileName,
234
            $outputFileName,
235
            $probabilityEstimates
236
        );
237
        $output = [];
238
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
239
240
        unlink($testSetFileName);
241
        unlink($modelFileName);
242
        $predictions = file_get_contents($outputFileName);
243
244
        unlink($outputFileName);
245
246
        if ($return !== 0) {
247
            throw LibsvmCommandException::failedToRun($command, array_pop($output));
0 ignored issues
show
Documentation introduced by
array_pop($output) is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
        }
249
250
        return $predictions;
251
    }
252
253
    private function getOSExtension(): string
254
    {
255
        $os = strtoupper(substr(PHP_OS, 0, 3));
256
        if ($os === 'WIN') {
257
            return '.exe';
258
        } elseif ($os === 'DAR') {
259
            return '-osx';
260
        }
261
262
        return '';
263
    }
264
265
    private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string
266
    {
267
        return sprintf(
268
            '%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s',
269
            $this->binPath,
270
            $this->getOSExtension(),
271
            $this->type,
272
            $this->kernel,
273
            $this->cost,
274
            $this->nu,
275
            $this->degree,
276
            $this->gamma !== null ? ' -g '.$this->gamma : '',
277
            $this->coef0,
278
            $this->epsilon,
279
            $this->cacheSize,
280
            $this->tolerance,
281
            $this->shrinking,
282
            $this->probabilityEstimates,
283
            escapeshellarg($trainingSetFileName),
284
            escapeshellarg($modelFileName)
285
        );
286
    }
287
288
    private function buildPredictCommand(
289
        string $testSetFileName,
290
        string $modelFileName,
291
        string $outputFileName,
292
        bool $probabilityEstimates
293
    ): string {
294
        return sprintf(
295
            '%ssvm-predict%s -b %d %s %s %s',
296
            $this->binPath,
297
            $this->getOSExtension(),
298
            $probabilityEstimates ? 1 : 0,
299
            escapeshellarg($testSetFileName),
300
            escapeshellarg($modelFileName),
301
            escapeshellarg($outputFileName)
302
        );
303
    }
304
305
    private function ensureDirectorySeparator(string &$path): void
306
    {
307
        if (substr($path, -1) !== DIRECTORY_SEPARATOR) {
308
            $path .= DIRECTORY_SEPARATOR;
309
        }
310
    }
311
312
    private function verifyBinPath(string $path): void
313
    {
314
        if (!is_dir($path)) {
315
            throw InvalidArgumentException::pathNotFound($path);
316
        }
317
318
        $osExtension = $this->getOSExtension();
319
        foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) {
320
            $filePath = $path.$filename.$osExtension;
321
            if (!file_exists($filePath)) {
322
                throw InvalidArgumentException::fileNotFound($filePath);
323
            }
324
325
            if (!is_executable($filePath)) {
326
                throw InvalidArgumentException::fileNotExecutable($filePath);
327
            }
328
        }
329
    }
330
}
331