Completed
Push — master ( 97e0ea...5aec63 )
by Peter
06:37
created

CommandExecutor::exec()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

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