Completed
Pull Request — master (#243)
by Дмитрий
05:05
created

ShellCommand::setFd()   C

Complexity

Conditions 10
Paths 35

Size

Total Lines 53
Code Lines 40

Duplication

Lines 8
Ratio 15.09 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 10
eloc 40
c 2
b 2
f 0
nc 35
nop 2
dl 8
loc 53
rs 6.5333

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
namespace PHPDaemon\Core;
3
4
use PHPDaemon\Core\CallbackWrapper;
5
use PHPDaemon\Core\Daemon;
6
use PHPDaemon\FS\File;
7
use PHPDaemon\Network\IOStream;
8
9
/**
10
 * Process
11
 * @package PHPDaemon\Core
12
 * @author  Vasily Zorin <[email protected]>
13
 */
14
class ShellCommand extends IOStream
15
{
16
17
    protected $finishWrite;
18
19
    /**
20
     * @var string Command string
21
     */
22
    protected $cmd;
23
24
    /**
25
     * @var string Executable path
26
     */
27
    public $binPath;
28
29
    /**
30
     * @var array Opened pipes
31
     */
32
    protected $pipes;
33
34
    /**
35
     * @var resource Process descriptor
36
     */
37
    protected $pd;
38
39
    /**
40
     * @var resource FD write
41
     */
42
    protected $fdWrite;
43
44
    /**
45
     * @var boolean Output errors?
46
     */
47
    protected $outputErrors = true;
48
49
    /**
50
     * @var string SUID
51
     */
52
    public $setUser;
53
54
    /**
55
     * @var string SGID
56
     */
57
    public $setGroup;
58
59
    /**
60
     * @var string Chroot
61
     */
62
    public $chroot = '/';
63
64
    /**
65
     * @var array Hash of environment's variables
66
     */
67
    protected $env = [];
68
69
    /**
70
     * @var string Chdir
71
     */
72
    public $cwd;
73
74
    /**
75
     * @var string Path to error logfile
76
     */
77
    protected $errlogfile = null;
78
79
    /**
80
     * @var array Array of arguments
81
     */
82
    protected $args;
83
84
    /**
85
     * @var integer Process priority
86
     */
87
    protected $nice;
88
89
    /**
90
     * @var \EventBufferEvent
91
     */
92
    protected $bevWrite;
93
94
    /**
95
     * @var \EventBufferEvent
96
     */
97
    protected $bevErr;
98
99
    /**
100
     * @var boolean Got EOF?
101
     */
102
    protected $EOF = false;
103
104
    /**
105
     * Get command string
106
     * @return string
107
     */
108
    public function getCmd()
109
    {
110
        return $this->cmd;
111
    }
112
113
    /**
114
     * Set group
115
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
116
     */
117
    public function setGroup($val)
118
    {
119
        $this->setGroup = $val;
120
        return $this;
121
    }
122
123
    /**
124
     * Set cwd
125
     * @param  string $dir
126
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
127
     */
128
    public function setCwd($dir)
129
    {
130
        $this->cwd = $dir;
131
        return $this;
132
    }
133
134
    /**
135
     * Set group
136
     * @param  string $val
137
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
138
     */
139
    public function setUser($val)
140
    {
141
        $this->setUser = $val;
142
        return $this;
143
    }
144
145
    /**
146
     * Set chroot
147
     * @param  string $dir
148
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
149
     */
150
    public function setChroot($dir)
151
    {
152
        $this->chroot = $dir;
153
        return $this;
154
    }
155
156
    /**
157
     * Execute
158
     * @param string $binPath Binpath
0 ignored issues
show
Documentation introduced by
Should the type for parameter $binPath not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
159
     * @param callable $cb Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
160
     * @param array $args Optional. Arguments
0 ignored issues
show
Documentation introduced by
Should the type for parameter $args not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
161
     * @param array $env Optional. Hash of environment's variables
0 ignored issues
show
Documentation introduced by
Should the type for parameter $env not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
162
     */
163
    public static function exec($binPath = null, $cb = null, $args = null, $env = null)
164
    {
165
        $o = new static;
166
        $data = '';
167
        $o->bind('read', function ($o) use (&$data, $o) {
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $o conflicts with one of the imported variables.
Loading history...
168
            $data .= $o->readUnlimited();
169
        });
170
        $o->bind('eof', function ($o) use (&$data, $cb) {
171
            $cb($o, $data);
172
            $o->close();
173
        });
174
        $o->execute($binPath, $args, $env);
175
    }
176
177
178
    /**
179
     * Sets fd
180
     * @param  resource $fd File descriptor
181
     * @param  \EventBufferEvent $bev
0 ignored issues
show
Documentation introduced by
Should the type for parameter $bev not be \EventBufferEvent|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
182
     * @return void
183
     */
184
    public function setFd($fd, $bev = null)
185
    {
186
        $this->fd = $fd;
187
        if ($fd === false) {
188
            $this->finish();
189
            return;
190
        }
191
        $this->fdWrite = $this->pipes[0];
192
        $flags = !is_resource($this->fd) ? \EventBufferEvent::OPT_CLOSE_ON_FREE : 0;
193
        $flags |= \EventBufferEvent::OPT_DEFER_CALLBACKS; /* buggy option */
194
195
        $this->bev = new \EventBufferEvent(
196
            Daemon::$process->eventBase,
197
            $this->fd,
198
            0,
199
            [$this, 'onReadEv'],
200
            null,
201
            [$this, 'onStateEv']
202
        );
203
        $this->bevWrite = new \EventBufferEvent(
204
            Daemon::$process->eventBase,
205
            $this->fdWrite,
206
            0,
207
            null,
208
            [$this, 'onWriteEv'],
209
            null
210
        );
211
        if (!$this->bev || !$this->bevWrite) {
212
            $this->finish();
213
            return;
214
        }
215
        if ($this->priority !== null) {
216
            $this->bev->priority = $this->priority;
217
        }
218
        if ($this->timeout !== null) {
219
            $this->setTimeout($this->timeout);
220
        }
221 View Code Duplication
        if (!$this->bev->enable(\Event::READ | \Event::TIMEOUT | \Event::PERSIST)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
            $this->finish();
223
            return;
224
        }
225 View Code Duplication
        if (!$this->bevWrite->enable(\Event::WRITE | \Event::TIMEOUT | \Event::PERSIST)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
            $this->finish();
227
            return;
228
        }
229
        $this->bev->setWatermark(\Event::READ, $this->lowMark, $this->highMark);
230
231
        init:
232
        if (!$this->inited) {
233
            $this->inited = true;
234
            $this->init();
235
        }
236
    }
237
238
    /**
239
     * Sets an array of arguments
240
     * @param  array Arguments
241
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
242
     */
243
    public function setArgs($args = null)
244
    {
245
        $this->args = $args;
246
247
        return $this;
248
    }
249
250
    /**
251
     * Set a hash of environment's variables
252
     * @param  array Hash of environment's variables
253
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
254
     */
255
    public function setEnv($env = null)
256
    {
257
        $this->env = $env;
258
259
        return $this;
260
    }
261
262
    /**
263
     * Called when got EOF
264
     * @return void
265
     */
266
    public function onEofEvent()
267
    {
268
        if ($this->EOF) {
269
            return;
270
        }
271
        $this->EOF = true;
272
273
        $this->event('eof');
274
    }
275
276
    /**
277
     * Set priority
278
     * @param  integer $nice Priority
0 ignored issues
show
Documentation introduced by
Should the type for parameter $nice not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
279
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
280
     */
281
    public function nice($nice = null)
282
    {
283
        $this->nice = $nice;
284
285
        return $this;
286
    }
287
288
    /**
289
     * Called when new data received
290
     * @return this|null
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
291
     */
292
    protected function onRead()
293
    {
294
        if (func_num_args() === 1) {
295
            $this->onRead = func_get_arg(0);
0 ignored issues
show
Documentation introduced by
The property onRead does not exist on object<PHPDaemon\Core\ShellCommand>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
296
            return $this;
297
        }
298
        $this->event('read');
299
    }
300
301
    /**
302
     * Build arguments string from associative/enumerated array (may be mixed)
303
     * @param  array $args
304
     * @return string
305
     */
306
    public static function buildArgs($args)
307
    {
308
        if (!is_array($args)) {
309
            return '';
310
        }
311
        $ret = '';
312
        foreach ($args as $k => $v) {
313
            if (!is_int($v) && ($v !== null)) {
314
                $v = escapeshellarg($v);
315
            }
316
            if (is_int($k)) {
317
                $ret .= ' ' . $v;
318
            } else {
319
                if ($k{0} !== '-') {
320
                    $ret .= ' --' . $k . ($v !== null ? '=' . $v : '');
321
                } else {
322
                    $ret .= ' ' . $k . ($v !== null ? '=' . $v : '');
323
                }
324
            }
325
        }
326
        return $ret;
327
    }
328
329
    /**
330
     * Execute
331
     * @param  string $binPath Optional. Binpath
0 ignored issues
show
Documentation introduced by
Should the type for parameter $binPath not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
332
     * @param  array $args Optional. Arguments
0 ignored issues
show
Documentation introduced by
Should the type for parameter $args not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
333
     * @param  array $env Optional. Hash of environment's variables
0 ignored issues
show
Documentation introduced by
Should the type for parameter $env not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
334
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
335
     */
336
    public function execute($binPath = null, $args = null, $env = null)
337
    {
338
        if ($binPath !== null) {
339
            $this->binPath = $binPath;
340
        }
341
342
        if ($env !== null) {
343
            $this->env = $env;
344
        }
345
346
        if ($args !== null) {
347
            $this->args = $args;
348
        }
349
        $this->cmd = $this->binPath . static::buildArgs($this->args) . ($this->outputErrors ? ' 2>&1' : '');
350
351
        if (isset($this->setUser) || isset($this->setGroup)) {
352
            if (isset($this->setUser) && isset($this->setGroup) && ($this->setUser !== $this->setGroup)) {
353
                $this->cmd = 'sudo -g ' . escapeshellarg($this->setGroup) . '  -u ' . escapeshellarg($this->setUser) . ' ' . $this->cmd;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
354
            } else {
355
                $this->cmd = 'su ' . escapeshellarg($this->setGroup) . ' -c ' . escapeshellarg($this->cmd);
356
            }
357
        }
358
359
        if ($this->chroot !== '/') {
360
            $this->cmd = 'chroot ' . escapeshellarg($this->chroot) . ' ' . $this->cmd;
361
        }
362
363
        if ($this->nice !== null) {
364
            $this->cmd = 'nice -n ' . ((int)$this->nice) . ' ' . $this->cmd;
365
        }
366
367
        $pipesDescr = [
368
            0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
369
            1 => ['pipe', 'w'] // stdout is a pipe that the child will write to
370
        ];
371
372
        if (($this->errlogfile !== null) && !$this->outputErrors) {
373
            $pipesDescr[2] = ['file', $this->errlogfile, 'a']; // @TODO: refactoring
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
374
        }
375
376
        $this->pd = proc_open($this->cmd, $pipesDescr, $this->pipes, $this->cwd, $this->env);
377
        if ($this->pd) {
378
            $this->setFd($this->pipes[1]);
379
        }
380
381
        return $this;
382
    }
383
384
    /**
385
     * Finish write stream
386
     * @return boolean
387
     */
388
    public function finishWrite()
389
    {
390
        if (!$this->writing) {
391
            $this->closeWrite();
392
        }
393
394
        $this->finishWrite = true;
395
396
        return true;
397
    }
398
399
    /**
400
     * Close the process
401
     * @return void
402
     */
403
    public function close()
404
    {
405
        parent::close();
406
        $this->closeWrite();
407
        if (is_resource($this->pd)) {
408
            proc_close($this->pd);
409
        }
410
    }
411
412
    /**
413
     * Called when stream is finished
414
     */
415
    public function onFinish()
416
    {
417
        $this->onEofEvent();
418
    }
419
420
    /**
421
     * Close write stream
422
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
423
     */
424
    public function closeWrite()
425
    {
426
        if ($this->bevWrite) {
427
            if (isset($this->bevWrite)) {
428
                $this->bevWrite->free();
429
            }
430
            $this->bevWrite = null;
431
        }
432
433
        if ($this->fdWrite) {
434
            fclose($this->fdWrite);
435
            $this->fdWrite = null;
436
        }
437
438
        return $this;
439
    }
440
441
    /**
442
     * Got EOF?
443
     * @return boolean
444
     */
445
    public function eof()
446
    {
447
        return $this->EOF;
448
    }
449
450
    /**
451
     * Send data to the connection. Note that it just writes to buffer that flushes at every baseloop
452
     * @param  string $data Data to send
453
     * @return boolean Success
454
     */
455 View Code Duplication
    public function write($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
456
    {
457
        if (!$this->alive) {
458
            Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
459
            return false;
460
        }
461
        if (!isset($this->bevWrite)) {
462
            return false;
463
        }
464
        if (!mb_orig_strlen($data)) {
465
            return true;
466
        }
467
        $this->writing = true;
468
        Daemon::$noError = true;
469
        if (!$this->bevWrite->write($data) || !Daemon::$noError) {
470
            $this->close();
471
            return false;
472
        }
473
        return true;
474
    }
475
476
    /**
477
     * Send data and appending \n to connection. Note that it just writes to buffer flushed at every baseloop
478
     * @param  string Data to send
479
     * @return boolean Success
480
     */
481 View Code Duplication
    public function writeln($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
482
    {
483
        if (!$this->alive) {
484
            Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
485
            return false;
486
        }
487
        if (!isset($this->bevWrite)) {
488
            return false;
489
        }
490
        if (!mb_orig_strlen($data) && !mb_orig_strlen($this->EOL)) {
491
            return true;
492
        }
493
        $this->writing = true;
494
        $this->bevWrite->write($data);
495
        $this->bevWrite->write($this->EOL);
496
        return true;
497
    }
498
499
    /**
500
     * Sets callback which will be called once when got EOF
501
     * @param  callable $cb
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
502
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be ShellCommand?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
503
     */
504
    public function onEOF($cb = null)
505
    {
506
        return $this->bind('eof', $cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 504 can also be of type null; however, PHPDaemon\Traits\EventHandlers::bind() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
507
    }
508
509
    public function getStatus()
510
    {
511
        return !empty($this->pd) ? proc_get_status($this->pd) : null;
512
    }
513
}
514