Process   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 363
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 75.76%

Importance

Changes 0
Metric Value
wmc 49
lcom 2
cbo 1
dl 0
loc 363
ccs 100
cts 132
cp 0.7576
rs 8.5454
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getPid() 0 4 1
A isStopped() 0 8 2
A isStarted() 0 4 1
A errno() 0 4 1
A errmsg() 0 4 1
A ifSignal() 0 4 1
B __construct() 0 17 7
A initStatus() 0 9 1
A name() 0 8 2
B start() 0 25 6
A isRunning() 0 5 1
D updateStatus() 0 39 9
A getCallable() 0 13 4
A signal() 0 6 1
A shutdown() 0 14 4
A wait() 0 12 4
A registerSignalHandler() 0 4 1
A dispatchSignal() 0 4 1
A run() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Process often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Process, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Jenner
5
 * Date: 2015/8/12
6
 * Time: 15:25
7
 */
8
9
namespace Jenner\SimpleFork;
10
11
class Process
12
{
13
    /**
14
     * @var Runnable|callable
15
     */
16
    protected $runnable;
17
18
    /**
19
     * @var int
20
     */
21
    protected $pid = 0;
22
23
    /**
24
     * @var string custom process name
25
     */
26
    protected $name = null;
27
28
    /**
29
     * @var bool if the process is started
30
     */
31
    protected $started = false;
32
33
    /**
34
     * @var bool
35
     */
36
    protected $running = false;
37
38
    /**
39
     * @var int the signal which made the process terminate
40
     */
41
    protected $term_signal = null;
42
43
    /**
44
     * @var int the signal which made the process stop
45
     */
46
    protected $stop_signal = null;
47
48
    /**
49
     * @var int error code
50
     */
51
    protected $errno = null;
52
53
    /**
54
     * @var string error message
55
     */
56
    protected $errmsg = null;
57
58
    /**
59
     * @var bool
60
     */
61
    protected $if_signal = false;
62
63
    /**
64
     * @var array
65
     */
66
    protected $callbacks = array();
67
68
    /**
69
     * @var array signal handlers
70
     */
71
    protected $signal_handlers = array();
72
73
74
    /**
75
     * @param string $execution it can be a Runnable object, callback function or null
76
     * @param null $name process name,you can manager the process by it's name.
77
     */
78 60
    public function __construct($execution = null, $name = null)
79
    {
80 60
        if (!is_null($execution) && $execution instanceof Runnable) {
81 9
            $this->runnable = $execution;
82 60
        } elseif (!is_null($execution) && is_callable($execution)) {
83 45
            $this->runnable = $execution;
84 54
        } elseif (!is_null($execution)) {
85
            throw new \InvalidArgumentException('param execution is not a object of Runnable or callable');
86
        } else {
87 12
            Utils::checkOverwriteRunMethod(get_class($this));
88
        }
89 57
        if (!is_null($name)) {
90 3
            $this->name = $name;
91 3
        }
92
93 57
        $this->initStatus();
94 57
    }
95
96
    /**
97
     * init process status
98
     */
99 57
    protected function initStatus()
100
    {
101 57
        $this->pid = null;
102 57
        $this->running = null;
103 57
        $this->term_signal = null;
104 57
        $this->stop_signal = null;
105 57
        $this->errno = null;
106 57
        $this->errmsg = null;
107 57
    }
108
109
    /**
110
     * get pid
111
     *
112
     * @return int
113
     */
114 9
    public function getPid()
115
    {
116 9
        return $this->pid;
117
    }
118
119
    /**
120
     * get or set name
121
     *
122
     * @param string|null $name
123
     * @return mixed
124
     */
125 3
    public function name($name = null)
126
    {
127 3
        if (!is_null($name)) {
128
            $this->name = $name;
129
        } else {
130 3
            return $this->name;
131
        }
132
    }
133
134
    /**
135
     * if the process is stopped
136
     *
137
     * @return bool
138
     */
139 3
    public function isStopped()
140
    {
141 3
        if (is_null($this->errno)) {
142 3
            return false;
143
        }
144
145 3
        return true;
146
    }
147
148
    /**
149
     * if the process is started
150
     *
151
     * @return bool
152
     */
153 15
    public function isStarted()
154
    {
155 15
        return $this->started;
156
    }
157
158
    /**
159
     * get pcntl errno
160
     *
161
     * @return int
162
     */
163 9
    public function errno()
164
    {
165 9
        return $this->errno;
166
    }
167
168
    /**
169
     * get pcntl errmsg
170
     *
171
     * @return string
172
     */
173 6
    public function errmsg()
174
    {
175 6
        return $this->errmsg;
176
    }
177
178 3
    public function ifSignal()
179
    {
180 3
        return $this->if_signal;
181
    }
182
183
    /**
184
     * start the sub process
185
     * and run the callback
186
     *
187
     * @return string pid
188
     */
189 54
    public function start()
190
    {
191 54
        if (!empty($this->pid) && $this->isRunning()) {
192
            throw new \LogicException("the process is already running");
193
        }
194
195 54
        $callback = $this->getCallable();
196
197 54
        $pid = pcntl_fork();
198 54
        if ($pid < 0) {
199
            throw new \RuntimeException("fork error");
200 54
        } elseif ($pid > 0) {
201 54
            $this->pid = $pid;
202 54
            $this->running = true;
203 54
            $this->started = true;
204 54
        } else {
205
            $this->pid = getmypid();
206
            $this->signal();
207
            foreach ($this->signal_handlers as $signal => $handler) {
208
                pcntl_signal($signal, $handler);
209
            }
210
            call_user_func($callback);
211
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
212
        }
213 54
    }
214
215
    /**
216
     * if the process is running
217
     *
218
     * @return bool
219
     */
220 54
    public function isRunning()
221
    {
222 54
        $this->updateStatus();
223 54
        return $this->running;
224
    }
225
226
    /**
227
     * update the process status
228
     *
229
     * @param bool $block
230
     */
231 54
    protected function updateStatus($block = false)
232
    {
233 54
        if ($this->running !== true) {
234 24
            return;
235
        }
236
237 54
        if ($block) {
238 12
            $res = pcntl_waitpid($this->pid, $status);
0 ignored issues
show
Bug introduced by
The variable $status does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
239 12
        } else {
240 54
            $res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED);
241
        }
242
243 54
        if ($res === -1) {
244
            throw new \RuntimeException('pcntl_waitpid failed. the process maybe available');
245 54
        } elseif ($res === 0) {
246 54
            $this->running = true;
247 54
        } else {
248 51
            if (pcntl_wifsignaled($status)) {
249 6
                $this->term_signal = pcntl_wtermsig($status);
250 6
            }
251 51
            if (pcntl_wifstopped($status)) {
252
                $this->stop_signal = pcntl_wstopsig($status);
253
            }
254 51
            if (pcntl_wifexited($status)) {
255 45
                $this->errno = pcntl_wexitstatus($status);
256 45
                $this->errmsg = pcntl_strerror($this->errno);
257 45
            } else {
258 6
                $this->errno = pcntl_get_last_error();
259 6
                $this->errmsg = pcntl_strerror($this->errno);
260
            }
261 51
            if (pcntl_wifsignaled($status)) {
262 6
                $this->if_signal = true;
263 6
            } else {
264 45
                $this->if_signal = false;
265
            }
266
267 51
            $this->running = false;
268
        }
269 54
    }
270
271
    /**
272
     * get sub process callback
273
     *
274
     * @return array|callable|null
275
     */
276 54
    protected function getCallable()
277
    {
278 54
        $callback = null;
0 ignored issues
show
Unused Code introduced by
$callback is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
279 54
        if (is_object($this->runnable) && $this->runnable instanceof Runnable) {
280 9
            $callback = array($this->runnable, 'run');
281 54
        } elseif (is_callable($this->runnable)) {
282 45
            $callback = $this->runnable;
283 45
        } else {
284 6
            $callback = array($this, 'run');
285
        }
286
287 54
        return $callback;
288
    }
289
290
    /**
291
     * register signal SIGTERM handler,
292
     * when the parent process call shutdown and use the default signal,
293
     * this handler will be triggered
294
     */
295
    protected function signal()
296
    {
297
        pcntl_signal(SIGTERM, function () {
298
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method signal() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
299
        });
300
    }
301
302
    /**
303
     * kill self
304
     *
305
     * @param bool|true $block
306
     * @param int $signal
307
     */
308 12
    public function shutdown($block = true, $signal = SIGTERM)
309
    {
310 12
        if (empty($this->pid)) {
311
            throw new \LogicException('the process pid is null, so maybe the process is not started');
312
        }
313 12
        if (!$this->isRunning()) {
314
            throw new \LogicException("the process is not running");
315
        }
316 12
        if (!posix_kill($this->pid, $signal)) {
317
            throw new \RuntimeException("kill son process failed");
318
        }
319
320 12
        $this->updateStatus($block);
321 12
    }
322
323
    /**
324
     * waiting for the sub process exit
325
     *
326
     * @param bool|true $block if block the process
327
     * @param int $sleep default 0.1s check sub process status
328
     * every $sleep milliseconds.
329
     */
330 33
    public function wait($block = true, $sleep = 100000)
331
    {
332 33
        while (true) {
333 33
            if ($this->isRunning() === false) {
334 33
                return;
335
            }
336 30
            if (!$block) {
337
                break;
338
            }
339 30
            usleep($sleep);
340 30
        }
341
    }
342
343
    /**
344
     * register sub process signal handler,
345
     * when the sub process start, the handlers will be registered
346
     *
347
     * @param $signal
348
     * @param callable $handler
349
     */
350
    public function registerSignalHandler($signal, callable $handler)
351
    {
352
        $this->signal_handlers[$signal] = $handler;
353
    }
354
355
    /**
356
     * after php-5.3.0, we can call pcntl_singal_dispatch to call signal handlers for pending signals
357
     * which can save cpu resources than using declare(tick=n)
358
     *
359
     * @return bool
360
     */
361
    public function dispatchSignal()
362
    {
363
        return pcntl_signal_dispatch();
364
    }
365
366
    /**
367
     * you should overwrite this function
368
     * if you do not use the Runnable or callback.
369
     */
370
    public function run()
371
    {
372
    }
373
}