Completed
Push — master ( fc854e...ca3b34 )
by Peter
15:53
created

CommandExecutor   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 33.96%

Importance

Changes 0
Metric Value
wmc 19
lcom 2
cbo 5
dl 0
loc 174
ccs 18
cts 53
cp 0.3396
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A exec() 0 7 2
A console() 0 4 1
A executeCommand() 0 8 2
A send() 0 18 3
A __construct() 0 10 2
A execute() 0 8 3
A executeCommandInBackground() 0 14 3
A prepare() 0 17 3
1
<?php
2
/**
3
 * AnimeDb package.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/GPL-3.0 GPL v3
8
 */
9
namespace AnimeDb\Bundle\AppBundle\Service;
10
11
use Symfony\Component\HttpFoundation\RequestStack;
12
use Symfony\Component\Routing\RouterInterface;
13
use Symfony\Component\Process\Process;
14
15
/**
16
 * Command executor.
17
 *
18
 * Example:
19
 * <code>
20
 *   php app/console cache:clear > /dev/null 2>&1
21
 *   php composer.phar update
22
 *   ping > ping.log
23
 * <code>
24
 */
25
class CommandExecutor
26
{
27
    /**
28
     * @var string
29
     */
30
    protected $host;
31
32
    /**
33
     * @var string
34
     */
35
    protected $path;
36
37
    /**
38
     * @var string
39
     */
40
    protected $cwd;
41
42
    /**
43
     * @var string
44
     */
45
    protected $console;
46
47
    /**
48
     * @var PhpFinder
49
     */
50
    protected $finder;
51
52
    /**
53
     * @var int
54
     */
55
    const TIMEOUT = 2;
56
57
    /**
58
     * @param PhpFinder $finder
59
     * @param RouterInterface $router
60
     * @param RequestStack $request_stack
61
     * @param string $root_dir
62
     */
63 5
    public function __construct(PhpFinder $finder, RouterInterface $router, RequestStack $request_stack, $root_dir)
64
    {
65 5
        $this->finder = $finder;
66 5
        $this->cwd = $root_dir.'/../';
67 5
        $this->console = escapeshellarg($root_dir.DIRECTORY_SEPARATOR.'console');
68 5
        $this->path = $router->generate('command_exec');
69 5
        if ($request = $request_stack->getCurrentRequest()) {
70 4
            $this->host = $request->getHost().':'.$request->getPort();
71
        }
72 5
    }
73
74
    /**
75
     * @deprecated see self::send()
76
     *
77
     * @throws \InvalidArgumentException
78
     *
79
     * @param string $command
80
     */
81
    public function exec($command)
82
    {
83
        if (!$command) {
84
            throw new \InvalidArgumentException('Unknown command');
85
        }
86
        $this->send('php app/console '.$command);
87
    }
88
89
    /**
90
     * Execute command.
91
     *
92
     * If timeout <= 0 and callback is null then command will be executed in background
93
     *
94
     * @param string $command
95
     * @param int $timeout
96
     * @param callable|null $callback
97
     */
98
    public function execute($command, $timeout = 300, $callback = null)
99
    {
100
        if ($timeout > 0 || is_callable($callback)) {
101
            $this->executeCommand($command, $timeout, $callback);
102
        } else {
103
            $this->executeCommandInBackground($command);
104
        }
105
    }
106
107
    /**
108
     * @param string $command
109
     * @param int $timeout
110
     * @param callable|null $callback
111
     */
112
    public function console($command, $timeout = 300, $callback = null)
113
    {
114
        $this->execute('php app/console '.$command, $timeout, $callback);
115
    }
116
117
    /**
118
     * @throws \RuntimeException
119
     *
120
     * @param string $command
121
     * @param int $timeout
122
     * @param callable|null $callback
123
     */
124
    protected function executeCommand($command, $timeout = 300, $callback = null)
125
    {
126
        $process = new Process($this->prepare($command), $this->cwd, null, null, $timeout);
127
        $process->run($callback);
128
        if (!$process->isSuccessful()) {
129
            throw new \RuntimeException(sprintf('An error occurred when executing the "%s" command.', $command));
130
        }
131
    }
132
133
    /**
134
     * @param string $command
135
     */
136
    protected function executeCommandInBackground($command)
137
    {
138
        $cwd = getcwd();
139
        chdir($this->cwd);
140
141
        $command = $this->prepare($command);
142
        if (defined('PHP_WINDOWS_VERSION_BUILD') && function_exists('popen')) {
143
            pclose(popen('start /b call '.$command, 'r'));
144
        } else {
145
            exec($command.' &');
146
        }
147
148
        chdir($cwd);
149
    }
150
151
    /**
152
     * Send the command to perform in a new thread.
153
     *
154
     * @param string $command
155
     * @param string $host
156
     */
157 1
    public function send($command, $host = '')
158
    {
159 1
        $host = $host ?: $this->host;
160 1
        if (!$host) {
161 1
            throw new \InvalidArgumentException('Unknown host that will run the command');
162
        }
163
        $content = 'command='.urlencode($command);
164
165
        $fp = fsockopen($this->host, 80, $errno, $errstr, self::TIMEOUT);
166
        $request = 'POST '.$this->path." HTTP/1.1\r\n";
167
        $request .= 'Host: '.$host."\r\n";
168
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
169
        $request .= 'Content-Length: '.strlen($content)."\r\n";
170
        $request .= "Connection: Close\r\n\r\n";
171
        $request .= $content;
172
        fwrite($fp, $request);
173
        fclose($fp);
174
    }
175
176
    /**
177
     * @param string $command
178
     *
179
     * @return string
180
     */
181 3
    public function prepare($command)
182
    {
183
        // change path for php
184 3
        if (substr($command, 0, 4) == 'php ') {
185 2
            $command = $this->finder->getPath().substr($command, 3);
186
        }
187
188
        // change path to console
189 3
        $command = str_replace(' app/console ', ' '.$this->console.' ', $command);
190
191
        // change /dev/null for Windows
192 3
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
193
            $command = str_replace('/dev/null', 'nul', $command);
194
        }
195
196 3
        return $command;
197
    }
198
}
199