Passed
Push — master ( 59cbeb...7ecc0d )
by Alexander
14:22 queued 12:10
created

Command   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Test Coverage

Coverage 95.88%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 29
eloc 82
dl 0
loc 285
ccs 93
cts 97
cp 0.9588
rs 10
c 4
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setCacheDuration() 0 5 1
A __construct() 0 10 1
A setCacheOverwrite() 0 5 1
A setCacheInvalidator() 0 5 1
A _getCacheKey() 0 13 4
B run() 0 41 10
A __toString() 0 3 1
A runLive() 0 3 1
A _createLiveOutputCallback() 0 24 5
A _doRun() 0 35 4
1
<?php
2
/**
3
 * This file is part of the SVN-Buddy library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/svn-buddy
9
 */
10
11
namespace ConsoleHelpers\SVNBuddy\Repository\Connector;
12
13
14
use ConsoleHelpers\ConsoleKit\ConsoleIO;
15
use ConsoleHelpers\SVNBuddy\Cache\CacheManager;
16
use ConsoleHelpers\SVNBuddy\Exception\RepositoryCommandException;
17
use ConsoleHelpers\SVNBuddy\Process\IProcessFactory;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Process\Exception\ProcessFailedException;
20
use Symfony\Component\Process\Process;
21
22
class Command
23
{
24
25
	/**
26
	 * Process factory.
27
	 *
28
	 * @var IProcessFactory
29
	 */
30
	private $_processFactory;
31
32
	/**
33
	 * Command line.
34
	 *
35
	 * @var array
36
	 */
37
	private $_commandLine;
38
39
	/**
40
	 * Console IO.
41
	 *
42
	 * @var ConsoleIO
43
	 */
44
	private $_io;
45
46
	/**
47
	 * Cache manager.
48
	 *
49
	 * @var CacheManager
50
	 */
51
	private $_cacheManager;
52
53
	/**
54
	 * Cache duration.
55
	 *
56
	 * @var mixed
57
	 */
58
	private $_cacheDuration;
59
60
	/**
61
	 * Text that when different from cached will invalidate the cache.
62
	 *
63
	 * @var string
64
	 */
65
	private $_cacheInvalidator;
66
67
	/**
68
	 * Overwrites cached value regardless of it's expiration/invalidation settings.
69
	 *
70
	 * @var boolean
71
	 */
72
	private $_cacheOverwrite = false;
73
74
	/**
75
	 * Creates a command instance.
76
	 *
77
	 * @param array           $command_line    Command line.
78
	 * @param ConsoleIO       $io              Console IO.
79
	 * @param CacheManager    $cache_manager   Cache manager.
80
	 * @param IProcessFactory $process_factory Process factory.
81
	 */
82 65
	public function __construct(
83
		array $command_line,
84
		ConsoleIO $io,
85
		CacheManager $cache_manager,
86
		IProcessFactory $process_factory
87
	) {
88 65
		$this->_commandLine = $command_line;
89 65
		$this->_io = $io;
90 65
		$this->_cacheManager = $cache_manager;
91 65
		$this->_processFactory = $process_factory;
92 65
	}
93
94
	/**
95
	 * Set cache invalidator.
96
	 *
97
	 * @param string $invalidator Invalidator.
98
	 *
99
	 * @return self
100
	 */
101 16
	public function setCacheInvalidator($invalidator)
102
	{
103 16
		$this->_cacheInvalidator = $invalidator;
104
105 16
		return $this;
106
	}
107
108
	/**
109
	 * Set cache duration.
110
	 *
111
	 * @param mixed $duration Duration (seconds if numeric OR whatever "strtotime" accepts).
112
	 *
113
	 * @return self
114
	 */
115 39
	public function setCacheDuration($duration)
116
	{
117 39
		$this->_cacheDuration = $duration;
118
119 39
		return $this;
120
	}
121
122
	/**
123
	 * Set cache overwrite.
124
	 *
125
	 * @param boolean $cache_overwrite Cache replace.
126
	 *
127
	 * @return self
128
	 */
129 4
	public function setCacheOverwrite($cache_overwrite)
130
	{
131 4
		$this->_cacheOverwrite = $cache_overwrite;
132
133 4
		return $this;
134
	}
135
136
	/**
137
	 * Runs the command.
138
	 *
139
	 * @param callable|null $callback Callback.
140
	 *
141
	 * @return string|\SimpleXMLElement
142
	 */
143 65
	public function run($callback = null)
144
	{
145 65
		$output = null;
146 65
		$cache_key = $this->_getCacheKey();
147
148 65
		if ( $cache_key ) {
149 47
			if ( $this->_cacheOverwrite ) {
150 2
				$this->_cacheManager->deleteCache($cache_key, $this->_cacheDuration);
151 2
			}
152
			else {
153 45
				$output = $this->_cacheManager->getCache($cache_key, $this->_cacheInvalidator, $this->_cacheDuration);
154
			}
155
156 47
			if ( isset($output) ) {
157 31
				if ( $this->_io->isVerbose() ) {
158
					$this->_io->writeln('<debug>[svn, cached]: ' . $this . '</debug>');
159
				}
160
161 31
				if ( $this->_io->isDebug() ) {
162
					$this->_io->writeln($output, OutputInterface::OUTPUT_RAW);
163
				}
164
165 31
				if ( is_callable($callback) ) {
166 6
					call_user_func($callback, Process::OUT, $output);
0 ignored issues
show
Bug introduced by
It seems like $callback can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
					call_user_func(/** @scrutinizer ignore-type */ $callback, Process::OUT, $output);
Loading history...
167 6
				}
168 31
			}
169 47
		}
170
171 65
		if ( !isset($output) ) {
172 34
			$output = $this->_doRun($callback);
173
174 33
			if ( $cache_key ) {
175 16
				$this->_cacheManager->setCache($cache_key, $output, $this->_cacheInvalidator, $this->_cacheDuration);
176 16
			}
177 33
		}
178
179 64
		if ( in_array('--xml', $this->_commandLine) ) {
180 14
			return simplexml_load_string($output);
181
		}
182
183 50
		return $output;
184
	}
185
186
	/**
187
	 * Returns cache key for a command.
188
	 *
189
	 * @return string
190
	 */
191 65
	private function _getCacheKey()
192
	{
193 65
		if ( $this->_cacheInvalidator || $this->_cacheDuration ) {
194 47
			$command_string = (string)$this;
195
196 47
			if ( preg_match(Connector::URL_REGEXP, $command_string, $regs) ) {
197 17
				return $regs[2] . $regs[3] . $regs[4] . '/command:' . $command_string;
198
			}
199
200 30
			return 'misc/command:' . $command_string;
201
		}
202
203 18
		return '';
204
	}
205
206
	/**
207
	 * Runs the command.
208
	 *
209
	 * @param callable|null $callback Callback.
210
	 *
211
	 * @return string
212
	 * @throws RepositoryCommandException When command execution failed.
213
	 */
214 34
	private function _doRun($callback = null)
215
	{
216 34
		$process = $this->_processFactory->createProcess($this->_commandLine, 180); // Idle timeout: 3 minutes.
217 34
		$command_string = (string)$this;
218
219
		try {
220 34
			if ( $this->_io->isVerbose() ) {
221 1
				$this->_io->writeln('');
222 1
				$this->_io->write('<debug>[svn, ' . date('H:i:s') . '... ]: ' . $command_string . '</debug>');
223
224 1
				$start = microtime(true);
225 1
				$process->mustRun($callback);
226
227 1
				$runtime = sprintf('%01.2f', microtime(true) - $start);
228 1
				$this->_io->write(
229 1
					"\033[2K\r" . '<debug>[svn, ' . round($runtime, 2) . 's]: ' . $command_string . '</debug>'
0 ignored issues
show
Bug introduced by
$runtime of type string is incompatible with the type double|integer expected by parameter $num of round(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

229
					"\033[2K\r" . '<debug>[svn, ' . round(/** @scrutinizer ignore-type */ $runtime, 2) . 's]: ' . $command_string . '</debug>'
Loading history...
230 1
				);
231 1
				$this->_io->writeln('');
232 1
			}
233
			else {
234 33
				$process->mustRun($callback);
235
			}
236
237 33
			$output = (string)$process->getOutput();
238
239 33
			if ( $this->_io->isDebug() ) {
240 1
				$this->_io->writeln($output, OutputInterface::OUTPUT_RAW);
241 1
			}
242
243 33
			return $output;
244
		}
245 1
		catch ( ProcessFailedException $e ) {
246 1
			throw new RepositoryCommandException(
247 1
				$command_string,
248 1
				$process->getErrorOutput()
249 1
			);
250
		}
251
	}
252
253
	/**
254
	 * Runs an svn command and displays output in real time.
255
	 *
256
	 * @param array $replacements Replacements for the output.
257
	 *
258
	 * @return string
259
	 */
260 2
	public function runLive(array $replacements = array())
261
	{
262 2
		return $this->run($this->_createLiveOutputCallback($replacements));
263
	}
264
265
	/**
266
	 * Creates "live output" callback.
267
	 *
268
	 * @param array $replacements Replacements for the output.
269
	 *
270
	 * @return callable
271
	 */
272 2
	private function _createLiveOutputCallback(array $replacements = array())
273
	{
274 2
		$io = $this->_io;
275
276 2
		$replace_froms = array_keys($replacements);
277 2
		$replace_tos = array_values($replacements);
278
279 2
		return function ($type, $buffer) use ($io, $replace_froms, $replace_tos) {
280 2
			foreach ( $replace_froms as $index => $replace_from ) {
281 2
				$replace_to = $replace_tos[$index];
282
283 2
				if ( substr($replace_from, 0, 1) === '/' && substr($replace_from, -1, 1) === '/' ) {
284 2
					$buffer = preg_replace($replace_from, $replace_to, $buffer);
285 2
				}
286
				else {
287 2
					$buffer = str_replace($replace_from, $replace_to, $buffer);
288
				}
289 2
			}
290
291 2
			if ( $type === Process::ERR ) {
292 1
				$buffer = '<error>ERR:</error> ' . $buffer;
293 1
			}
294
295 2
			$io->write($buffer);
296 2
		};
297
	}
298
299
	/**
300
	 * Returns a string representation of a command.
301
	 *
302
	 * @return string
303
	 */
304 65
	public function __toString()
305
	{
306 65
		return implode(' ', $this->_commandLine);
307
	}
308
309
}
310