Driver_Phantomjs_Connection   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 97.47%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 4
dl 0
loc 259
ccs 77
cts 79
cp 0.9747
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A port() 0 15 3
A server() 0 9 2
A host() 0 4 1
A pid() 0 4 1
A __construct() 0 7 2
A start() 0 25 4
A is_started() 0 4 1
A is_running() 0 4 1
A stop() 0 18 4
A pid_file() 0 4 1
A get() 0 4 1
A post() 0 8 1
A delete() 0 7 1
A command_url() 0 4 1
A call() 0 31 3
1
<?php
2
3
namespace Openbuildings\Spiderling;
4
5
/**
6
 * Connect to phantomjs service, optionally start one if not present on a new port.
7
 * Send requests to phantomjs
8
 *
9
 * @package    Openbuildings\Spiderling
10
 * @author     Ivan Kerin
11
 * @copyright  (c) 2013 OpenBuildings Ltd.
12
 * @license    http://spdx.org/licenses/BSD-3-Clause
13
 */
14
class Driver_Phantomjs_Connection {
15
16
	/**
17
	 * The file storing the pid of the current phantomjs server process
18
	 * @var string
19
	 */
20
	protected $_pid_file;
21
22
	/**
23
	 * The pid of the current phantomjs server process
24
	 * @var string
25
	 */
26
	protected $_pid;
27
28
	/**
29
	 * Ulr of the phantomjs server
30
	 * @var string
31
	 */
32
	protected $_server = 'http://localhost';
33
34
	/**
35
	 * Port of the phantomjs server
36
	 * @var string
37
	 */
38
	protected $_port;
39
40
	/**
41
	 * Getter / Setter of the phantomjs server port.
42
	 * If none is set it tries to fine an unused port between 4445 and 5000
43
	 * @param  string $port
44
	 * @return string|Driver_Phantomjs_Connection
45
	 */
46 15
	public function port($port = NULL)
47
	{
48 15
		if ($port !== NULL)
49
		{
50 2
			$this->_port = $port;
51 2
			return $this;
52
		}
53
54 15
		if ( ! $this->_port)
55
		{
56 2
			$this->_port = Network::ephimeral_port($this->host(), 4445, 5000);
0 ignored issues
show
Security Bug introduced by
It seems like $this->host() targeting Openbuildings\Spiderling...omjs_Connection::host() can also be of type false; however, Openbuildings\Spiderling\Network::ephimeral_port() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
Documentation Bug introduced by
It seems like \Openbuildings\Spiderlin...is->host(), 4445, 5000) of type integer or boolean is incompatible with the declared type string of property $_port.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
57
		}
58
59 15
		return $this->_port;
60
	}
61
62
	/**
63
	 * Getter / Setter of the current phantomjs server url
64
	 * @param  string $server
65
	 * @return string|Driver_Phantomjs_Connection
66
	 */
67 15
	public function server($server = NULL)
68
	{
69 15
		if ($server !== NULL)
70
		{
71 2
			$this->_server = $server;
72 2
			return $this;
73
		}
74 15
		return $this->_server;
75
	}
76
77
	/**
78
	 * Get the host of the current phantomjs server (without the protocol part)
79
	 * @return string
80
	 */
81 2
	public function host()
82
	{
83 2
		return parse_url($this->server(), PHP_URL_HOST);
84
	}
85
86
	/**
87
	 * Getter, get the current phantomjs server process pid
88
	 * @return string
89
	 */
90 1
	public function pid()
91
	{
92 1
		return $this->_pid;
93
	}
94
95 3
	public function __construct($server = NULL)
96
	{
97 3
		if ($server)
98
		{
99 2
			$this->server($server);
100
		}
101 3
	}
102
103
	/**
104
	 * Start a new phantomjs server, optionally provide pid_file and log file.
105
	 * If you provide a pid_file, it will kill the process currently running on that pid, before starting the new one
106
	 * If the start is unsuccessfull it will return FALSE
107
	 * @param  string $pid_file
108
	 * @param  string $log_file
109
	 * @return boolean
110
	 */
111 2
	public function start($pid_file = NULL, $log_file = '/dev/null')
112
	{
113 2
		if ($pid_file)
0 ignored issues
show
Bug Best Practice introduced by
The expression $pid_file of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
114
		{
115 1
			$this->_pid_file = $pid_file;
116 1
			if (is_file($this->_pid_file))
117
			{
118 1
				Phantomjs::kill(file_get_contents($pid_file));
119 1
				unlink($this->_pid_file);
120
			}
121
		}
122
123 2
		$this->_pid = Phantomjs::start('phantom.js', $this->port(), 'phantomjs-connection.js', $log_file);
124
125 2
		if ($this->_pid_file)
126
		{
127 1
			file_put_contents($this->_pid_file, $this->_pid);
128
		}
129
130 2
		$self = $this;
131
132
		return Attempt::make(function() use ($self) {
133 2
			return $self->is_running();
134 2
		});
135
	}
136
137
	/**
138
	 * Check if the phantomjs server has been started
139
	 * @return boolean
140
	 */
141 2
	public function is_started()
142
	{
143 2
		return (bool) $this->_pid;
144
	}
145
146
	/**
147
	 * Check if the phantomjs server is actually running (the port is taken)
148
	 * @return boolean
149
	 */
150 2
	public function is_running()
151
	{
152 2
		return ! Network::is_port_open($this->host(), $this->port());
0 ignored issues
show
Security Bug introduced by
It seems like $this->host() targeting Openbuildings\Spiderling...omjs_Connection::host() can also be of type false; however, Openbuildings\Spiderling\Network::is_port_open() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
153
	}
154
155
	/**
156
	 * Gracefully stop the phantomjs server. Return FALSE on failure. Clear the pid_file if set
157
	 * @return boolean
158
	 */
159 2
	public function stop()
160
	{
161 2
		if ($this->is_started())
162
		{
163 2
			$this->delete('session');
164 2
			$this->_pid = NULL;
165
		}
166
167 2
		if ($this->_pid_file AND is_file($this->_pid_file))
168
		{
169 1
			unlink($this->_pid_file);
170
		}
171 2
		$self = $this;
172
173
		return Attempt::make(function() use ($self) {
174 2
			return ! $self->is_running();
175 2
		});
176
	}
177
178
	/**
179
	 * Getter - get the current pid_file
180
	 * @return string
181
	 */
182 1
	public function pid_file()
183
	{
184 1
		return $this->_pid_file;
185
	}
186
187
	/**
188
	 * Perform a get request on the phantomjs server
189
	 * @param  string $command
190
	 * @return mixed
191
	 */
192 10
	public function get($command)
193
	{
194 10
		return $this->call($command);
195
	}
196
197
	/**
198
	 * Perform a post request on the phantomjs server
199
	 * @param  string $command
200
	 * @param  array  $params
201
	 * @return mixed
202
	 */
203 10
	public function post($command, array $params)
204
	{
205 10
		$options = array();
206 10
		$options[CURLOPT_POST] = TRUE;
207 10
		$options[CURLOPT_POSTFIELDS] = http_build_query($params);
208
209 10
		return $this->call($command, $options);
210
	}
211
212
	/**
213
	 * Perform a delete request on the phantomjs server
214
	 * @param  string $command
215
	 * @return mixed
216
	 */
217 3
	public function delete($command)
218
	{
219 3
		$options = array();
220 3
		$options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
221
222 3
		return $this->call($command, $options);
223
	}
224
225
	/**
226
	 * Get the full url of a command (including server and port)
227
	 * @param  string $command
228
	 * @return string
229
	 */
230 15
	public function command_url($command)
231
	{
232 15
		return rtrim($this->server(), '/').':'.$this->port().'/'.$command;
233
	}
234
235
	/**
236
	 * Perform a custom request on the phantomjs server, using curl
237
	 * @param  string $command
238
	 * @param  array  $options
239
	 * @return mixed
240
	 */
241 14
	protected function call($command, array $options = array())
242
	{
243 14
		$curl = curl_init();
244 14
		$options[CURLOPT_URL] = $this->command_url($command);
245 14
		$options[CURLOPT_RETURNTRANSFER] = TRUE;
246 14
		$options[CURLOPT_FOLLOWLOCATION] = TRUE;
247
248 14
		curl_setopt_array($curl, $options);
249
250 14
		$raw = '';
251
252
		Attempt::make(function() use ($curl, & $raw) {
253 14
			$raw = trim(curl_exec($curl));
254 14
			return curl_getinfo($curl, CURLINFO_HTTP_CODE) == 200;
255 14
		});
256
257 14
		$error = curl_error($curl);
258 14
		$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
259
260 14
		curl_close($curl);
261
262 14
		if ($error)
263
			throw new Exception_Driver('Curl ":command" throws exception :error', array(':command' => $command, ':error' => $error));
264
265 14
		if ($code != 200)
266
			throw new Exception_Driver('Unexpected response from the panthomjs for :command: :code', array(':command' => $command, ':code' => $code));
267
268 14
		$result = json_decode($raw, TRUE);
269
270 14
		return $result;
271
	}
272
}
273