Completed
Push — master ( 9d2d91...54e726 )
by Sébastien
12:01
created

StandaloneServer   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 2

Test Coverage

Coverage 74.17%

Importance

Changes 7
Bugs 1 Features 0
Metric Value
wmc 46
c 7
b 1
f 0
lcom 3
cbo 2
dl 0
loc 351
ccs 112
cts 151
cp 0.7417
rs 8.4

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A getCommand() 0 18 1
C stop() 0 37 8
A isStarted() 0 4 1
A getPid() 0 14 3
A restart() 0 5 1
A getServerPort() 0 4 1
A setServerPort() 0 4 1
A getConfig() 0 4 1
A getDefaultConfig() 0 11 2
C checkConfigRequiredArgs() 0 40 14
C start() 0 52 8
A isPortAvailable() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like StandaloneServer 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 StandaloneServer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PjbServer\Tools;
4
5
/*
6
java -Djava.awt.headless="true"
7
     -Dphp.java.bridge.threads=50
8
     -Dphp.java.bridge.base=/usr/lib/php/modules
9
     -Dphp.java.bridge.php_exec=/usr/local/bin/php-cgi
10
     -Dphp.java.bridge.default_log_file=
11
     -Dphp.java.bridge.default_log_level=5
12
     -Dphp.java.bridge.daemon="false"
13
     -jar JavaBridge.jar
14
*/
15
16
17
class StandaloneServer
18
{
19
20
    /**
21
     * @var int
22
     */
23
    protected $port;
24
25
    /**
26
     *
27
     * @var array
28
     */
29
    protected $config;
30
31
    /**
32
     * Tells whether the standalone server is started
33
     * @var boolean
34
     */
35
    protected $started = false;
36
37
    /**
38
     * @var array
39
     */
40
    protected $required_arguments = array(
41
        'port' => 'FILTER_VALIDATE_INT',
42
        'server_jar' => 'existing_file',
43
        'log_file' => 'file_with_existing_directory',
44
        'pid_file' => 'file_with_existing_directory',
45
    );
46
47
    /**
48
     * Default configuration options
49
     * @var array
50
     */
51
    protected $default_config = array(
52
        'server_jar' => '{base_dir}/resources/pjb621_standalone/JavaBridge.jar',
53
        'java_bin' => 'java',
54
        'log_file' => '{base_dir}/resources/pjb621_standalone/logs/pjbserver-port{tcp_port}.log',
55
        'pid_file' => '{base_dir}/resources/pjb621_standalone/var/run/pjbserver-port{tcp_port}.pid'
56
    );
57
58
    /**
59
     * Constructor
60
     *
61
     * <code>
62
     *
63
     * $config = array(
64
     *      'port' => 8089,
65
     *
66
     *      // optionally
67
     *      'server_jar' => 'path/to/JavaBridge.jar'
68
     *      'java_bin' => 'path/to/java'
69
     * );
70
     * $server = new StandaloneServer($config);
71
     *
72
     * </code>
73
     *
74
     * @throws Exception\InvalidArgumentException
75
     * @param array $config
76
     */
77 10
    public function __construct(array $config)
78
    {
79 10
        if (!isset($config['port'])) {
80 3
            throw new Exception\InvalidArgumentException("Error missing required 'port' in config");
81 7
        } elseif (!filter_var($config['port'], FILTER_VALIDATE_INT)) {
82 1
            throw new Exception\InvalidArgumentException("Option 'port' must be numeric");
83
        }
84 6
        $config = array_merge($this->getDefaultConfig($config['port']), $config);
85 6
        $this->checkConfigRequiredArgs($config);
86 5
        $this->setServerPort($config['port']);
87 5
        $this->config = $config;
88 5
    }
89
90
    /**
91
     * Start the standalone server
92
     *
93
     * @throws Exception\RuntimeException
94
     *
95
     * @return void
96
     */
97 3
    public function start()
98
    {
99
100 3
        if ($this->isStarted()) {
101
            return;
102
        }
103
104 3
        $port = $this->getServerPort();
105
106 3
        if (!$this->isPortAvailable('localhost', $port)) {
107
            $msg = "Cannot start server on port '$port', it's already in use.";
108
            throw new Exception\RuntimeException($msg);
109
        }
110
111 3
        $command = $this->getCommand();
112
113 3
        $log_file = $this->config['log_file'];
114 3
        $pid_file = $this->config['pid_file'];
115 3
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
116
117 3
        exec($cmd);
118
119 3
        if (!file_exists($pid_file)) {
120
            $msg = "Server not started, pid file was not created in '$pid_file'";
121
            throw new Exception\RuntimeException($msg);
122
        }
123 3
        if (!file_exists($log_file)) {
124
            $msg = "Server not started, log file was not created in '$log_file'";
125
            throw new Exception\RuntimeException($msg);
126
        }
127
128
        // Loop for waiting correct start of phpjavabridge
129 3
        $started = false;
130 3
        $refresh_ms = 200; // 200ms
131 3
        while (!$started) {
132 3
            usleep($refresh_ms);
133 3
            $log_file_content = file_get_contents($log_file);
134 3
            if (preg_match('/Exception/', $log_file_content)) {
135
                $msg = "Cannot start standalone server on port '$port', reason:\n";
136
                $msg .= $log_file_content;
137
                throw new Exception\RuntimeException($msg);
138
            }
139
140 3
            clearstatcache($clear_realpath_cache = false, $log_file);
141 3
            $log_file_content = file_get_contents($log_file);
142 3
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
143 3
                $started = true;
144 3
            }
145
146 3
        }
147 3
        $this->started = true;
148 3
    }
149
150
    /**
151
     * Check if TCP port is available for binding
152
     *
153
     * @param int $port
154
     * @param int $timeout
155
     */
156 3
    protected function isPortAvailable($host, $port, $timeout = 1)
157
    {
158 3
        $available = false;
159 3
        $fp = @stream_socket_client("tcp://$host:$port", $errno, $errstr, $timeout);
160 3
        if (!$fp) {
161 3
            $available = true;
162 3
        } else {
163
            fclose($fp);
164
        }
165 3
        return $available;
166
    }
167
168
    /**
169
     * Return command used to start the standalone server
170
     * @return string
171
     */
172 3
    public function getCommand()
173
    {
174 3
        $port = $this->getServerPort();
175
176 3
        $java_bin = $this->config['java_bin'];
177
178 3
        $classpath = join(':', array(
179 3
            $this->config['server_jar'],
180 3
        ));
181
182 3
        $directives = ' -D' . join(' -D', array(
183 3
            'php.java.bridge.daemon="false"',
184
            'php.java.bridge.threads=50'
185 3
        ));
186
187 3
        $command = "$java_bin -cp $classpath $directives php.java.bridge.Standalone SERVLET:$port";
188 3
        return $command;
189
    }
190
191
    /**
192
     * Stop the standalone server
193
     *
194
     * @throws Exception\RuntimeException
195
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
196
     * @return void
197
     */
198 3
    public function stop($throws_exception = false)
199
    {
200 3
        $pid_file = $this->config['pid_file'];
201
202
        try {
203 3
            $pid = $this->getPid();
204 3
        } catch (Exception\RuntimeException $e) {
205
            $msg = "Cannot stop server, pid cannot be determined (was the server started ?)";
206
            if ($throws_exception) {
207
                throw new Exception\RuntimeException($msg);
208
            }
209
        }
210
211 3
        $cmd = "kill $pid";
212
213 3
        exec($cmd, $output, $return_var);
214
215
        try {
216 3
            if ($return_var !== 0) {
217
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
218
                throw new Exception\RuntimeException($msg);
219
            }
220 3
        } catch (Exception\RuntimeException $e) {
221
            if ($throws_exception) {
222
                if (file_exists($pid_file)) {
223
                    unlink($pid_file);
224
                }
225
                throw $e;
226
            }
227
        }
228
229 3
        if (file_exists($pid_file)) {
230 3
            unlink($pid_file);
231 3
        }
232
233 3
        $this->started = false;
234 3
    }
235
236
    /**
237
     * Tells whether the standalone server is started
238
     * @return boolean
239
     */
240 3
    public function isStarted()
241
    {
242 3
        return $this->started;
243
    }
244
245
    /**
246
     * Get runnin standalone server pid number
247
     *
248
     * @throws Exception\RuntimeException
249
     * @return int
250
     */
251 3
    public function getPid()
252
    {
253 3
        $pid_file = $this->config['pid_file'];
254 3
        if (!file_exists($pid_file)) {
255
            $msg = "Pid file cannot be found '$pid_file'";
256
            throw new Exception\RuntimeException($msg);
257
        }
258 3
        $pid = trim(file_get_contents($pid_file));
259 3
        if (!is_numeric($pid)) {
260
            $msg = "Pid found '$pid_file' but no valid pid stored in it.";
261
            throw new Exception\RuntimeException($msg);
262
        }
263 3
        return $pid;
264
    }
265
266
    /**
267
     * Restart the standalone server
268
     */
269
    public function restart()
270
    {
271
        $this->stop();
272
        $this->start();
273
    }
274
275
    /**
276
     * Return port on which standalone server listens
277
     * @return int
278
     */
279 4
    public function getServerPort()
280
    {
281 4
        return $this->port;
282
    }
283
284
    /**
285
     * Set port on which standalone server listens.
286
     *
287
     * @param int $port
288
     * @return void
289
     */
290 5
    protected function setServerPort($port)
291
    {
292 5
        $this->port = $port;
293 5
    }
294
295
    /**
296
     * Return standalone configuration
297
     *
298
     * @return array
299
     */
300 3
    public function getConfig()
301
    {
302 3
        return $this->config;
303
    }
304
305
    /**
306
     * Return default configuration options
307
     * @param int $port
308
     * @return array
309
     */
310 6
    protected function getDefaultConfig($port)
311
    {
312 6
        $base_dir = realpath(__DIR__ . '/../../../');
313 6
        $config = array();
314 6
        foreach ($this->default_config as $key => $value) {
315 6
            $tmp = str_replace('{base_dir}', $base_dir, $value);
316 6
            $tmp = str_replace('{tcp_port}', $port, $tmp);
317 6
            $config[$key] = $tmp;
318 6
        }
319 6
        return $config;
320
    }
321
322
    /**
323
     * Check configuration parameters
324
     * @throws Exception\InvalidArgumentException
325
     * @param array $config
326
     */
327 6
    protected function checkConfigRequiredArgs(array $config)
328
    {
329 6
        foreach ($this->required_arguments as $name => $type) {
330 6
            if (!isset($config[$name])) {
331
                $msg = "Missing option '$name' in Standalone server configuration";
332
                throw new Exception\InvalidArgumentException($msg);
333
            }
334 6
            if (is_long($type) && !filter_var($config[$name], $type)) {
335
                $msg = "Unsupported type in option '$name' in Standalone server configuration (required $type)";
336
                throw new Exception\InvalidArgumentException($msg);
337 6
            } elseif (is_string($type)) {
338
                switch ($type) {
339 6
                    case 'existing_file':
340 6
                        $file = $config[$name];
341 6
                        if (!file_exists($file)) {
342 1
                            $msg = "The '$name' file '$file 'does not exists.";
343 1
                            throw new Exception\InvalidArgumentException($msg);
344
                        }
345 5
                        break;
346 6
                    case 'file_with_existing_directory':
347 5
                        $file = $config[$name];
348 5
                        $info = pathinfo($file);
349 5
                        $dirname = $info['dirname'];
350 5
                        if (!is_dir($dirname) || $dirname == ".") {
351
                            $msg = "Option '$name' refer to an invalid or non-existent directory ($file)";
352
                            throw new Exception\InvalidArgumentException($msg);
353
                        }
354 5
                        if (is_dir($file)) {
355
                            $msg = "Option '$name' does not refer to a file but an existing directory ($file)";
356
                            throw new Exception\InvalidArgumentException($msg);
357
                        }
358 5
                        if (file_exists($file) && !is_writable($file)) {
359
                            $msg = "File specified in '$name' is not writable ($file)";
360
                            throw new Exception\InvalidArgumentException($msg);
361
                        }
362 5
                        break;
363
                }
364 6
            }
365 6
        }
366 5
    }
367
}
368