Passed
Push — master ( 7cfee1...18dfb5 )
by Alexander
07:21 queued 05:09
created

Command::run()   B

Complexity

Conditions 9
Paths 66

Size

Total Lines 37
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.0117

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 19
c 2
b 0
f 0
dl 0
loc 37
ccs 18
cts 19
cp 0.9474
rs 8.0555
cc 9
nc 66
nop 1
crap 9.0117
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
			}
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 ( is_callable($callback) ) {
162 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

162
					call_user_func(/** @scrutinizer ignore-type */ $callback, Process::OUT, $output);
Loading history...
163
				}
164
			}
165
		}
166
167 65
		if ( !isset($output) ) {
168 34
			$output = $this->_doRun($callback);
169
170 33
			if ( $cache_key ) {
171 16
				$this->_cacheManager->setCache($cache_key, $output, $this->_cacheInvalidator, $this->_cacheDuration);
172
			}
173
		}
174
175 64
		if ( in_array('--xml', $this->_commandLine) ) {
176 14
			return simplexml_load_string($output);
177
		}
178
179 50
		return $output;
180
	}
181
182
	/**
183
	 * Returns cache key for a command.
184
	 *
185
	 * @return string
186
	 */
187 65
	private function _getCacheKey()
188
	{
189 65
		if ( $this->_cacheInvalidator || $this->_cacheDuration ) {
190 47
			$command_string = (string)$this;
191
192 47
			if ( preg_match(Connector::URL_REGEXP, $command_string, $regs) ) {
193 17
				return $regs[2] . $regs[3] . $regs[4] . '/command:' . $command_string;
194
			}
195
196 30
			return 'misc/command:' . $command_string;
197
		}
198
199 18
		return '';
200
	}
201
202
	/**
203
	 * Runs the command.
204
	 *
205
	 * @param callable|null $callback Callback.
206
	 *
207
	 * @return string
208
	 * @throws RepositoryCommandException When command execution failed.
209
	 */
210 34
	private function _doRun($callback = null)
211
	{
212 34
		$process = $this->_processFactory->createProcess($this->_commandLine, 1200);
213 34
		$command_string = (string)$this;
214
215
		try {
216 34
			$start = microtime(true);
217 34
			$process->mustRun($callback);
218
219 33
			if ( $this->_io->isVerbose() ) {
220 1
				$runtime = sprintf('%01.2f', microtime(true) - $start);
221 1
				$this->_io->writeln(
222 1
					array('', '<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

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