Passed
Pull Request — master (#218)
by Yuji
02:26
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\LibsvmCommandException;
9
use Phpml\Helper\Trainable;
10
11
class SupportVectorMachine
12
{
13
    use Trainable;
14
15
    /**
16
     * @var int
17
     */
18
    private $type;
19
20
    /**
21
     * @var int
22
     */
23
    private $kernel;
24
25
    /**
26
     * @var float
27
     */
28
    private $cost;
29
30
    /**
31
     * @var float
32
     */
33
    private $nu;
34
35
    /**
36
     * @var int
37
     */
38
    private $degree;
39
40
    /**
41
     * @var float|null
42
     */
43
    private $gamma;
44
45
    /**
46
     * @var float
47
     */
48
    private $coef0;
49
50
    /**
51
     * @var float
52
     */
53
    private $epsilon;
54
55
    /**
56
     * @var float
57
     */
58
    private $tolerance;
59
60
    /**
61
     * @var int
62
     */
63
    private $cacheSize;
64
65
    /**
66
     * @var bool
67
     */
68
    private $shrinking;
69
70
    /**
71
     * @var bool
72
     */
73
    private $probabilityEstimates;
74
75
    /**
76
     * @var string
77
     */
78
    private $binPath;
79
80
    /**
81
     * @var string
82
     */
83
    private $varPath;
84
85
    /**
86
     * @var string
87
     */
88
    private $model;
89
90
    /**
91
     * @var array
92
     */
93
    private $targets = [];
94
95
    public function __construct(
96
        int $type,
97
        int $kernel,
98
        float $cost = 1.0,
99
        float $nu = 0.5,
100
        int $degree = 3,
101
        ?float $gamma = null,
102
        float $coef0 = 0.0,
103
        float $epsilon = 0.1,
104
        float $tolerance = 0.001,
105
        int $cacheSize = 100,
106
        bool $shrinking = true,
107
        bool $probabilityEstimates = false
108
    ) {
109
        $this->type = $type;
110
        $this->kernel = $kernel;
111
        $this->cost = $cost;
112
        $this->nu = $nu;
113
        $this->degree = $degree;
114
        $this->gamma = $gamma;
115
        $this->coef0 = $coef0;
116
        $this->epsilon = $epsilon;
117
        $this->tolerance = $tolerance;
118
        $this->cacheSize = $cacheSize;
119
        $this->shrinking = $shrinking;
120
        $this->probabilityEstimates = $probabilityEstimates;
121
122
        $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR;
123
124
        $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR;
125
        $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR;
126
    }
127
128
    public function setBinPath(string $binPath): void
129
    {
130
        $this->ensureDirectorySeparator($binPath);
131
        $this->verifyBinPath($binPath);
132
133
        $this->binPath = $binPath;
134
    }
135
136
    public function setVarPath(string $varPath): void
137
    {
138
        if (!is_writable($varPath)) {
139
            throw InvalidArgumentException::pathNotWritable($varPath);
140
        }
141
142
        $this->ensureDirectorySeparator($varPath);
143
        $this->varPath = $varPath;
144
    }
145
146
    public function train(array $samples, array $targets): void
147
    {
148
        $this->samples = array_merge($this->samples, $samples);
149
        $this->targets = array_merge($this->targets, $targets);
150
151
        $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR]));
152
        file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet);
153
        $modelFileName = $trainingSetFileName.'-model';
154
155
        $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName);
156
        $output = [];
157
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
158
159
        unlink($trainingSetFileName);
160
161
        if ($return !== 0) {
162
            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...
163
        }
164
165
        $this->model = file_get_contents($modelFileName);
166
167
        unlink($modelFileName);
168
    }
169
170
    public function getModel(): string
171
    {
172
        return $this->model;
173
    }
174
175
    /**
176
     * @return array|string
177
     *
178
     * @throws LibsvmCommandException
179
     */
180
    public function predict(array $samples)
181
    {
182
        $predictions = $this->runSvmPredict($samples, false);
183
184
        if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
185
            $predictions = DataTransformer::predictions($predictions, $this->targets);
186
        } else {
187
            $predictions = explode(PHP_EOL, trim($predictions));
188
        }
189
190
        if (!is_array($samples[0])) {
191
            return $predictions[0];
192
        }
193
194
        return $predictions;
195
    }
196
197
    /**
198
     * @return array|string
199
     *
200
     * @throws LibsvmCommandException
201
     */
202
    public function predictProbability(array $samples)
203
    {
204
        $predictions = $this->runSvmPredict($samples, true);
205
206
        if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
207
            $predictions = DataTransformer::probabilities($predictions, $this->targets);
208
        } else {
209
            $predictions = explode(PHP_EOL, trim($predictions));
210
        }
211
212
        if (!is_array($samples[0])) {
213
            return $predictions[0];
214
        }
215
216
        return $predictions;
217
    }
218
219
    private function runSvmPredict(array $samples, bool $probabilityEstimates): string
220
    {
221
        $testSet = DataTransformer::testSet($samples);
222
        file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet);
223
        file_put_contents($modelFileName = $testSetFileName.'-model', $this->model);
224
        $outputFileName = $testSetFileName.'-output';
225
226
        $command = $this->buildPredictCommand(
227
            $testSetFileName,
228
            $modelFileName,
229
            $outputFileName,
230
            $probabilityEstimates
231
        );
232
        $output = [];
233
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
234
235
        unlink($testSetFileName);
236
        unlink($modelFileName);
237
        $predictions = file_get_contents($outputFileName);
238
239
        unlink($outputFileName);
240
241
        if ($return !== 0) {
242
            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...
243
        }
244
245
        return $predictions;
246
    }
247
248
    private function getOSExtension(): string
249
    {
250
        $os = strtoupper(substr(PHP_OS, 0, 3));
251
        if ($os === 'WIN') {
252
            return '.exe';
253
        } elseif ($os === 'DAR') {
254
            return '-osx';
255
        }
256
257
        return '';
258
    }
259
260
    private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string
261
    {
262
        return sprintf(
263
            '%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',
264
            $this->binPath,
265
            $this->getOSExtension(),
266
            $this->type,
267
            $this->kernel,
268
            $this->cost,
269
            $this->nu,
270
            $this->degree,
271
            $this->gamma !== null ? ' -g '.$this->gamma : '',
272
            $this->coef0,
273
            $this->epsilon,
274
            $this->cacheSize,
275
            $this->tolerance,
276
            $this->shrinking,
277
            $this->probabilityEstimates,
278
            escapeshellarg($trainingSetFileName),
279
            escapeshellarg($modelFileName)
280
        );
281
    }
282
283
    private function buildPredictCommand(
284
        string $testSetFileName,
285
        string $modelFileName,
286
        string $outputFileName,
287
        bool $probabilityEstimates
288
    ): string {
289
        return sprintf(
290
            '%ssvm-predict%s -b %d %s %s %s',
291
            $this->binPath,
292
            $this->getOSExtension(),
293
            $probabilityEstimates ? 1 : 0,
294
            escapeshellarg($testSetFileName),
295
            escapeshellarg($modelFileName),
296
            escapeshellarg($outputFileName)
297
        );
298
    }
299
300
    private function ensureDirectorySeparator(string &$path): void
301
    {
302
        if (substr($path, -1) !== DIRECTORY_SEPARATOR) {
303
            $path .= DIRECTORY_SEPARATOR;
304
        }
305
    }
306
307
    private function verifyBinPath(string $path): void
308
    {
309
        if (!is_dir($path)) {
310
            throw InvalidArgumentException::pathNotFound($path);
311
        }
312
313
        $osExtension = $this->getOSExtension();
314
        foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) {
315
            $filePath = $path.$filename.$osExtension;
316
            if (!file_exists($filePath)) {
317
                throw InvalidArgumentException::fileNotFound($filePath);
318
            }
319
320
            if (!is_executable($filePath)) {
321
                throw InvalidArgumentException::fileNotExecutable($filePath);
322
            }
323
        }
324
    }
325
}
326