parent
e823a39a5c
commit
fd4f13f5e0
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
namespace Sabre\VObject\Parser; |
||||
|
||||
/** |
||||
* Note that most MimeDir related tests can actually be found in the ReaderTest |
||||
* class one level up. |
||||
*/ |
||||
class MimeDirTest extends \PHPUnit_Framework_TestCase { |
||||
|
||||
/** |
||||
* @expectedException \Sabre\VObject\ParseException |
||||
*/ |
||||
function testParseError() { |
||||
|
||||
$mimeDir = new MimeDir(); |
||||
$mimeDir->parse(fopen(__FILE__,'a')); |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@ |
||||
<?php |
||||
|
||||
namespace Sabre\VObject; |
||||
|
||||
class TestCase extends \PHPUnit_Framework_TestCase { |
||||
|
||||
/** |
||||
* This method tests wether two vcards or icalendar objects are |
||||
* semantically identical. |
||||
* |
||||
* It supports objects being supplied as strings, streams or |
||||
* Sabre\VObject\Component instances. |
||||
* |
||||
* PRODID is removed from both objects as this is often variable. |
||||
* |
||||
* @param resource|string|Component $expected |
||||
* @param resource|string|Component $actual |
||||
* @param string $message |
||||
*/ |
||||
function assertVObjEquals($expected, $actual, $message = '') { |
||||
|
||||
$self = $this; |
||||
$getObj = function($input) use ($self) { |
||||
|
||||
if (is_resource($input)) { |
||||
$input = stream_get_contents($input); |
||||
} |
||||
if (is_string($input)) { |
||||
$input = Reader::read($input); |
||||
} |
||||
if (!$input instanceof Component) { |
||||
$this->fail('Input must be a string, stream or VObject component'); |
||||
} |
||||
unset($input->PRODID); |
||||
return $input; |
||||
|
||||
}; |
||||
|
||||
$expected = $getObj($expected); |
||||
$actual = $getObj($actual); |
||||
|
||||
$this->assertEquals( |
||||
$expected->serialize(), |
||||
$actual->serialize(), |
||||
$message |
||||
); |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,127 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Console\Helper; |
||||
|
||||
/** |
||||
* Helps outputting debug information when running an external program from a command. |
||||
* |
||||
* An external program can be a Process, an HTTP request, or anything else. |
||||
* |
||||
* @author Fabien Potencier <fabien@symfony.com> |
||||
*/ |
||||
class DebugFormatterHelper extends Helper |
||||
{ |
||||
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'); |
||||
private $started = array(); |
||||
private $count = -1; |
||||
|
||||
/** |
||||
* Starts a debug formatting session |
||||
* |
||||
* @param string $id The id of the formatting session |
||||
* @param string $message The message to display |
||||
* @param string $prefix The prefix to use |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function start($id, $message, $prefix = 'RUN') |
||||
{ |
||||
$this->started[$id] = array('border' => ++$this->count % count($this->colors)); |
||||
|
||||
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message); |
||||
} |
||||
|
||||
/** |
||||
* Adds progress to a formatting session |
||||
* |
||||
* @param string $id The id of the formatting session |
||||
* @param string $buffer The message to display |
||||
* @param bool $error Whether to consider the buffer as error |
||||
* @param string $prefix The prefix for output |
||||
* @param string $errorPrefix The prefix for error output |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') |
||||
{ |
||||
$message = ''; |
||||
|
||||
if ($error) { |
||||
if (isset($this->started[$id]['out'])) { |
||||
$message .= "\n"; |
||||
unset($this->started[$id]['out']); |
||||
} |
||||
if (!isset($this->started[$id]['err'])) { |
||||
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix); |
||||
$this->started[$id]['err'] = true; |
||||
} |
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer); |
||||
} else { |
||||
if (isset($this->started[$id]['err'])) { |
||||
$message .= "\n"; |
||||
unset($this->started[$id]['err']); |
||||
} |
||||
if (!isset($this->started[$id]['out'])) { |
||||
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix); |
||||
$this->started[$id]['out'] = true; |
||||
} |
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer); |
||||
} |
||||
|
||||
return $message; |
||||
} |
||||
|
||||
/** |
||||
* Stops a formatting session |
||||
* |
||||
* @param string $id The id of the formatting session |
||||
* @param string $message The message to display |
||||
* @param bool $successful Whether to consider the result as success |
||||
* @param string $prefix The prefix for the end output |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function stop($id, $message, $successful, $prefix = 'RES') |
||||
{ |
||||
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; |
||||
|
||||
if ($successful) { |
||||
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); |
||||
} |
||||
|
||||
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); |
||||
|
||||
unset($this->started[$id]['out'], $this->started[$id]['err']); |
||||
|
||||
return $message; |
||||
} |
||||
|
||||
/** |
||||
* @param string $id The id of the formatting session |
||||
* |
||||
* @return string |
||||
*/ |
||||
private function getBorder($id) |
||||
{ |
||||
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return 'debug_formatter'; |
||||
} |
||||
} |
@ -0,0 +1,142 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Console\Helper; |
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface; |
||||
use Symfony\Component\Process\Exception\ProcessFailedException; |
||||
use Symfony\Component\Process\Process; |
||||
use Symfony\Component\Process\ProcessBuilder; |
||||
|
||||
/** |
||||
* The ProcessHelper class provides helpers to run external processes. |
||||
* |
||||
* @author Fabien Potencier <fabien@symfony.com> |
||||
*/ |
||||
class ProcessHelper extends Helper |
||||
{ |
||||
/** |
||||
* Runs an external process. |
||||
* |
||||
* @param OutputInterface $output An OutputInterface instance |
||||
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run |
||||
* @param string|null $error An error message that must be displayed if something went wrong |
||||
* @param callable|null $callback A PHP callback to run whenever there is some |
||||
* output available on STDOUT or STDERR |
||||
* @param int $verbosity The threshold for verbosity |
||||
* |
||||
* @return Process The process that ran |
||||
*/ |
||||
public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) |
||||
{ |
||||
$formatter = $this->getHelperSet()->get('debug_formatter'); |
||||
|
||||
if (is_array($cmd)) { |
||||
$process = ProcessBuilder::create($cmd)->getProcess(); |
||||
} elseif ($cmd instanceof Process) { |
||||
$process = $cmd; |
||||
} else { |
||||
$process = new Process($cmd); |
||||
} |
||||
|
||||
if ($verbosity <= $output->getVerbosity()) { |
||||
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); |
||||
} |
||||
|
||||
if ($output->isDebug()) { |
||||
$callback = $this->wrapCallback($output, $process, $callback); |
||||
} |
||||
|
||||
$process->run($callback); |
||||
|
||||
if ($verbosity <= $output->getVerbosity()) { |
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); |
||||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); |
||||
} |
||||
|
||||
if (!$process->isSuccessful() && null !== $error) { |
||||
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error))); |
||||
} |
||||
|
||||
return $process; |
||||
} |
||||
|
||||
/** |
||||
* Runs the process. |
||||
* |
||||
* This is identical to run() except that an exception is thrown if the process |
||||
* exits with a non-zero exit code. |
||||
* |
||||
* @param OutputInterface $output An OutputInterface instance |
||||
* @param string|Process $cmd An instance of Process or a command to run |
||||
* @param string|null $error An error message that must be displayed if something went wrong |
||||
* @param callable|null $callback A PHP callback to run whenever there is some |
||||
* output available on STDOUT or STDERR |
||||
* |
||||
* @return Process The process that ran |
||||
* |
||||
* @throws ProcessFailedException |
||||
* |
||||
* @see run() |
||||
*/ |
||||
public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) |
||||
{ |
||||
$process = $this->run($output, $cmd, $error, $callback); |
||||
|
||||
if (!$process->isSuccessful()) { |
||||
throw new ProcessFailedException($process); |
||||
} |
||||
|
||||
return $process; |
||||
} |
||||
|
||||
/** |
||||
* Wraps a Process callback to add debugging output. |
||||
* |
||||
* @param OutputInterface $output An OutputInterface interface |
||||
* @param Process $process The Process |
||||
* @param callable|null $callback A PHP callable |
||||
* |
||||
* @return callable |
||||
*/ |
||||
public function wrapCallback(OutputInterface $output, Process $process, $callback = null) |
||||
{ |
||||
$formatter = $this->getHelperSet()->get('debug_formatter'); |
||||
|
||||
$that = $this; |
||||
|
||||
return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { |
||||
$output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); |
||||
|
||||
if (null !== $callback) { |
||||
call_user_func($callback, $type, $buffer); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* This method is public for PHP 5.3 compatibility, it should be private. |
||||
* |
||||
* @internal |
||||
*/ |
||||
public function escapeString($str) |
||||
{ |
||||
return str_replace('<', '\\<', $str); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return 'process'; |
||||
} |
||||
} |
@ -1,17 +1,17 @@ |
||||
<info>Console Tool</info> |
||||
|
||||
<comment>Usage:</comment> |
||||
[options] command [arguments] |
||||
[options] command [arguments] |
||||
|
||||
<comment>Options:</comment> |
||||
<info>--help</info> <info>-h</info> Display this help message. |
||||
<info>--quiet</info> <info>-q</info> Do not output any message. |
||||
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> <info>-V</info> Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question. |
||||
<info>--help</info> (-h) Display this help message. |
||||
<info>--quiet</info> (-q) Do not output any message. |
||||
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> (-V) Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> (-n) Do not ask any interactive question. |
||||
|
||||
<comment>Available commands:</comment> |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
|
@ -1,22 +1,22 @@ |
||||
<info>My Symfony application</info> version <comment>v1.0</comment> |
||||
|
||||
<comment>Usage:</comment> |
||||
[options] command [arguments] |
||||
[options] command [arguments] |
||||
|
||||
<comment>Options:</comment> |
||||
<info>--help</info> <info>-h</info> Display this help message. |
||||
<info>--quiet</info> <info>-q</info> Do not output any message. |
||||
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> <info>-V</info> Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question. |
||||
<info>--help</info> (-h) Display this help message. |
||||
<info>--quiet</info> (-q) Do not output any message. |
||||
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> (-V) Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> (-n) Do not ask any interactive question. |
||||
|
||||
<comment>Available commands:</comment> |
||||
<info>alias1 </info> command 1 description |
||||
<info>alias2 </info> command 1 description |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
<info>alias1 </info> command 1 description |
||||
<info>alias2 </info> command 1 description |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
<comment>descriptor</comment> |
||||
<info>descriptor:command1 </info> command 1 description |
||||
<info>descriptor:command2 </info> command 2 description |
||||
<info>descriptor:command1 </info> command 1 description |
||||
<info>descriptor:command2 </info> command 2 description |
||||
|
@ -1,20 +1,20 @@ |
||||
<info>Console Tool</info> |
||||
|
||||
<comment>Usage:</comment> |
||||
[options] command [arguments] |
||||
[options] command [arguments] |
||||
|
||||
<comment>Options:</comment> |
||||
<info>--help</info> <info>-h</info> Display this help message. |
||||
<info>--quiet</info> <info>-q</info> Do not output any message. |
||||
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> <info>-V</info> Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question. |
||||
<info>--help</info> (-h) Display this help message. |
||||
<info>--quiet</info> (-q) Do not output any message. |
||||
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> (-V) Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> (-n) Do not ask any interactive question. |
||||
|
||||
<comment>Available commands:</comment> |
||||
<info>afoobar </info> The foo:bar command |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
<info>afoobar </info> The foo:bar command |
||||
<info>help </info> Displays help for a command |
||||
<info>list </info> Lists commands |
||||
<comment>foo</comment> |
||||
<info>foo:bar </info> The foo:bar command |
||||
<info>foo:bar </info> The foo:bar command |
||||
|
@ -1,16 +1,16 @@ |
||||
<info>Console Tool</info> |
||||
|
||||
<comment>Usage:</comment> |
||||
[options] command [arguments] |
||||
[options] command [arguments] |
||||
|
||||
<comment>Options:</comment> |
||||
<info>--help</info> <info>-h</info> Display this help message. |
||||
<info>--quiet</info> <info>-q</info> Do not output any message. |
||||
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> <info>-V</info> Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question. |
||||
<info>--help</info> (-h) Display this help message. |
||||
<info>--quiet</info> (-q) Do not output any message. |
||||
<info>--verbose</info> (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> (-V) Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> (-n) Do not ask any interactive question. |
||||
|
||||
<comment>Available commands for the "foo" namespace:</comment> |
||||
<info>foo:bar </info> The foo:bar command |
||||
<info>foo:bar </info> The foo:bar command |
||||
|
@ -1,13 +1 @@ |
||||
<info>Console Tool</info> |
||||
|
||||
<comment>Usage:</comment> |
||||
[options] command [arguments] |
||||
|
||||
<comment>Options:</comment> |
||||
<info>--help</info> <info>-h</info> Display this help message. |
||||
<info>--quiet</info> <info>-q</info> Do not output any message. |
||||
<info>--verbose</info> <info>-v|vv|vvv</info> Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
<info>--version</info> <info>-V</info> Display this application version. |
||||
<info>--ansi</info> Force ANSI output. |
||||
<info>--no-ansi</info> Disable ANSI output. |
||||
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question. |
||||
<info>Console Tool</info> |
@ -1,17 +1,17 @@ |
||||
Console Tool |
||||
|
||||
Usage: |
||||
[options] command [arguments] |
||||
[options] command [arguments] |
||||
|
||||
Options: |
||||
--help -h Display this help message. |
||||
--quiet -q Do not output any message. |
||||
--verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
--version -V Display this application version. |
||||
--ansi Force ANSI output. |
||||
--no-ansi Disable ANSI output. |
||||
--no-interaction -n Do not ask any interactive question. |
||||
--help (-h) Display this help message. |
||||
--quiet (-q) Do not output any message. |
||||
--verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug. |
||||
--version (-V) Display this application version. |
||||
--ansi Force ANSI output. |
||||
--no-ansi Disable ANSI output. |
||||
--no-interaction (-n) Do not ask any interactive question. |
||||
|
||||
Available commands: |
||||
help Displays help for a command |
||||
list Lists commands |
||||
help Displays help for a command |
||||
list Lists commands |
||||
|
@ -0,0 +1,118 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Console\Tests\Helper; |
||||
|
||||
use Symfony\Component\Console\Helper\DebugFormatterHelper; |
||||
use Symfony\Component\Console\Helper\HelperSet; |
||||
use Symfony\Component\Console\Helper\Helper; |
||||
use Symfony\Component\Console\Output\StreamOutput; |
||||
use Symfony\Component\Console\Helper\ProcessHelper; |
||||
use Symfony\Component\Process\Process; |
||||
|
||||
class ProcessHelperTest extends \PHPUnit_Framework_TestCase |
||||
{ |
||||
/** |
||||
* @dataProvider provideCommandsAndOutput |
||||
*/ |
||||
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error) |
||||
{ |
||||
$helper = new ProcessHelper(); |
||||
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); |
||||
$output = $this->getOutputStream($verbosity); |
||||
$helper->run($output, $cmd, $error); |
||||
$this->assertEquals($expected, $this->getOutput($output)); |
||||
} |
||||
|
||||
public function testPassedCallbackIsExecuted() |
||||
{ |
||||
$helper = new ProcessHelper(); |
||||
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); |
||||
$output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL); |
||||
|
||||
$executed = false; |
||||
$callback = function () use (&$executed) { $executed = true; }; |
||||
|
||||
$helper->run($output, 'php -r "echo 42;"', null, $callback); |
||||
$this->assertTrue($executed); |
||||
} |
||||
|
||||
public function provideCommandsAndOutput() |
||||
{ |
||||
$successOutputVerbose = <<<EOT |
||||
RUN php -r "echo 42;" |
||||
RES Command ran successfully |
||||
|
||||
EOT; |
||||
$successOutputDebug = <<<EOT |
||||
RUN php -r "echo 42;" |
||||
OUT 42 |
||||
RES Command ran successfully |
||||
|
||||
EOT; |
||||
$successOutputDebugWithTags = <<<EOT |
||||
RUN php -r "echo \"<info>42</info>\";" |
||||
OUT <info>42</info> |
||||
RES Command ran successfully |
||||
|
||||
EOT; |
||||
$successOutputProcessDebug = <<<EOT |
||||
RUN 'php' '-r' 'echo 42;' |
||||
OUT 42 |
||||
RES Command ran successfully |
||||
|
||||
EOT; |
||||
$syntaxErrorOutputVerbose = <<<EOT |
||||
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);" |
||||
RES 252 Command did not run successfully |
||||
|
||||
EOT; |
||||
$syntaxErrorOutputDebug = <<<EOT |
||||
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);" |
||||
ERR error message |
||||
OUT out message |
||||
RES 252 Command did not run successfully |
||||
|
||||
EOT; |
||||
|
||||
$errorMessage = 'An error occurred'; |
||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) { |
||||
$successOutputProcessDebug = str_replace("'", '"', $successOutputProcessDebug); |
||||
} |
||||
|
||||
return array( |
||||
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null), |
||||
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), |
||||
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null), |
||||
array($successOutputDebugWithTags, 'php -r "echo \"<info>42</info>\";"', StreamOutput::VERBOSITY_DEBUG, null), |
||||
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null), |
||||
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), |
||||
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null), |
||||
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage), |
||||
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage), |
||||
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage), |
||||
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null), |
||||
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null), |
||||
); |
||||
} |
||||
|
||||
private function getOutputStream($verbosity) |
||||
{ |
||||
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false); |
||||
} |
||||
|
||||
private function getOutput(StreamOutput $output) |
||||
{ |
||||
rewind($output->getStream()); |
||||
|
||||
return stream_get_contents($output->getStream()); |
||||
} |
||||
} |
@ -0,0 +1,111 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Filesystem; |
||||
|
||||
use Symfony\Component\Filesystem\Exception\IOException; |
||||
|
||||
/** |
||||
* LockHandler class provides a simple abstraction to lock anything by means of |
||||
* a file lock. |
||||
* |
||||
* A locked file is created based on the lock name when calling lock(). Other |
||||
* lock handlers will not be able to lock the same name until it is released |
||||
* (explicitly by calling release() or implicitly when the instance holding the |
||||
* lock is destroyed). |
||||
* |
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info> |
||||
* @author Romain Neutron <imprec@gmail.com> |
||||
* @author Nicolas Grekas <p@tchwork.com> |
||||
*/ |
||||
class LockHandler |
||||
{ |
||||
private $file; |
||||
private $handle; |
||||
|
||||
/** |
||||
* @param string $name The lock name |
||||
* @param string|null $lockPath The directory to store the lock. Default values will use temporary directory |
||||
* @throws IOException If the lock directory could not be created or is not writable |
||||
*/ |
||||
public function __construct($name, $lockPath = null) |
||||
{ |
||||
$lockPath = $lockPath ?: sys_get_temp_dir(); |
||||
|
||||
if (!is_dir($lockPath)) { |
||||
$fs = new Filesystem(); |
||||
$fs->mkdir($lockPath); |
||||
} |
||||
|
||||
if (!is_writable($lockPath)) { |
||||
throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); |
||||
} |
||||
|
||||
$this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); |
||||
} |
||||
|
||||
/** |
||||
* Lock the resource |
||||
* |
||||
* @param bool $blocking wait until the lock is released |
||||
* @return bool Returns true if the lock was acquired, false otherwise |
||||
* @throws IOException If the lock file could not be created or opened |
||||
*/ |
||||
public function lock($blocking = false) |
||||
{ |
||||
if ($this->handle) { |
||||
return true; |
||||
} |
||||
|
||||
// Silence both userland and native PHP error handlers |
||||
$errorLevel = error_reporting(0); |
||||
set_error_handler('var_dump', 0); |
||||
|
||||
if (!$this->handle = fopen($this->file, 'r')) { |
||||
if ($this->handle = fopen($this->file, 'x')) { |
||||
chmod($this->file, 0444); |
||||
} elseif (!$this->handle = fopen($this->file, 'r')) { |
||||
usleep(100); // Give some time for chmod() to complete |
||||
$this->handle = fopen($this->file, 'r'); |
||||
} |
||||
} |
||||
restore_error_handler(); |
||||
error_reporting($errorLevel); |
||||
|
||||
if (!$this->handle) { |
||||
$error = error_get_last(); |
||||
throw new IOException($error['message'], 0, null, $this->file); |
||||
} |
||||
|
||||
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see |
||||
// https://bugs.php.net/54129 |
||||
if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { |
||||
fclose($this->handle); |
||||
$this->handle = null; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Release the resource |
||||
*/ |
||||
public function release() |
||||
{ |
||||
if ($this->handle) { |
||||
flock($this->handle, LOCK_UN | LOCK_NB); |
||||
fclose($this->handle); |
||||
$this->handle = null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
<?php |
||||
|
||||
namespace Symfony\Component\Filesystem\Tests; |
||||
|
||||
use Symfony\Component\Filesystem\LockHandler; |
||||
|
||||
class LockHandlerTest extends \PHPUnit_Framework_TestCase |
||||
{ |
||||
/** |
||||
* @expectedException Symfony\Component\Filesystem\Exception\IOException |
||||
* @expectedExceptionMessage Failed to create "/a/b/c/d/e": mkdir(): Permission denied. |
||||
*/ |
||||
public function testConstructWhenRepositoryDoesNotExist() |
||||
{ |
||||
new LockHandler('lock', '/a/b/c/d/e'); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException Symfony\Component\Filesystem\Exception\IOException |
||||
* @expectedExceptionMessage The directory "/" is not writable. |
||||
*/ |
||||
public function testConstructWhenRepositoryIsNotWriteable() |
||||
{ |
||||
new LockHandler('lock', '/'); |
||||
} |
||||
|
||||
public function testConstructSanitizeName() |
||||
{ |
||||
$lock = new LockHandler('<?php echo "% hello word ! %" ?>');
|
||||
|
||||
$file = sprintf('%s/sf.-php-echo-hello-word-.4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f.lock', sys_get_temp_dir()); |
||||
// ensure the file does not exist before the lock |
||||
@unlink($file); |
||||
|
||||
$lock->lock(); |
||||
|
||||
$this->assertFileExists($file); |
||||
|
||||
$lock->release(); |
||||
} |
||||
|
||||
public function testLockRelease() |
||||
{ |
||||
$name = 'symfony-test-filesystem.lock'; |
||||
|
||||
$l1 = new LockHandler($name); |
||||
$l2 = new LockHandler($name); |
||||
|
||||
$this->assertTrue($l1->lock()); |
||||
$this->assertFalse($l2->lock()); |
||||
|
||||
$l1->release(); |
||||
|
||||
$this->assertTrue($l2->lock()); |
||||
$l2->release(); |
||||
} |
||||
|
||||
public function testLockTwice() |
||||
{ |
||||
$name = 'symfony-test-filesystem.lock'; |
||||
|
||||
$lockHandler = new LockHandler($name); |
||||
|
||||
$this->assertTrue($lockHandler->lock()); |
||||
$this->assertTrue($lockHandler->lock()); |
||||
|
||||
$lockHandler->release(); |
||||
} |
||||
|
||||
public function testLockIsReleased() |
||||
{ |
||||
$name = 'symfony-test-filesystem.lock'; |
||||
|
||||
$l1 = new LockHandler($name); |
||||
$l2 = new LockHandler($name); |
||||
|
||||
$this->assertTrue($l1->lock()); |
||||
$this->assertFalse($l2->lock()); |
||||
|
||||
$l1 = null; |
||||
|
||||
$this->assertTrue($l2->lock()); |
||||
$l2->release(); |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Process\Pipes; |
||||
|
||||
/** |
||||
* @author Romain Neutron <imprec@gmail.com> |
||||
* |
||||
* @internal |
||||
*/ |
||||
abstract class AbstractPipes implements PipesInterface |
||||
{ |
||||
/** @var array */ |
||||
public $pipes = array(); |
||||
|
||||
/** @var string */ |
||||
protected $inputBuffer = ''; |
||||
/** @var resource|null */ |
||||
protected $input; |
||||
|
||||
/** @var bool */ |
||||
private $blocked = true; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function close() |
||||
{ |
||||
foreach ($this->pipes as $pipe) { |
||||
fclose($pipe); |
||||
} |
||||
$this->pipes = array(); |
||||
} |
||||
|
||||
/** |
||||
* Returns true if a system call has been interrupted. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
protected function hasSystemCallBeenInterrupted() |
||||
{ |
||||
$lastError = error_get_last(); |
||||
|
||||
// stream_select returns false when the `select` system call is interrupted by an incoming signal |
||||
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); |
||||
} |
||||
|
||||
/** |
||||
* Unblocks streams |
||||
*/ |
||||
protected function unblock() |
||||
{ |
||||
if (!$this->blocked) { |
||||
return; |
||||
} |
||||
|
||||
foreach ($this->pipes as $pipe) { |
||||
stream_set_blocking($pipe, 0); |
||||
} |
||||
if (null !== $this->input) { |
||||
stream_set_blocking($this->input, 0); |
||||
} |
||||
|
||||
$this->blocked = false; |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Process\Pipes; |
||||
|
||||
/** |
||||
* PipesInterface manages descriptors and pipes for the use of proc_open. |
||||
* |
||||
* @author Romain Neutron <imprec@gmail.com> |
||||
* |
||||
* @internal |
||||
*/ |
||||
interface PipesInterface |
||||
{ |
||||
const CHUNK_SIZE = 16384; |
||||
|
||||
/** |
||||
* Returns an array of descriptors for the use of proc_open. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getDescriptors(); |
||||
|
||||
/** |
||||
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files. |
||||
* |
||||
* @return string[] |
||||
*/ |
||||
public function getFiles(); |
||||
|
||||
/** |
||||
* Reads data in file handles and pipes. |
||||
* |
||||
* @param bool $blocking Whether to use blocking calls or not. |
||||
* @param bool $close Whether to close pipes if they've reached EOF. |
||||
* |
||||
* @return string[] An array of read data indexed by their fd. |
||||
*/ |
||||
public function readAndWrite($blocking, $close = false); |
||||
|
||||
/** |
||||
* Returns if the current state has open file handles or pipes. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function areOpen(); |
||||
|
||||
/** |
||||
* Closes file handles and pipes. |
||||
*/ |
||||
public function close(); |
||||
} |
@ -0,0 +1,214 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Process\Pipes; |
||||
|
||||
use Symfony\Component\Process\Process; |
||||
|
||||
/** |
||||
* UnixPipes implementation uses unix pipes as handles. |
||||
* |
||||
* @author Romain Neutron <imprec@gmail.com> |
||||
* |
||||
* @internal |
||||
*/ |
||||
class UnixPipes extends AbstractPipes |
||||
{ |
||||
/** @var bool */ |
||||
private $ttyMode; |
||||
/** @var bool */ |
||||
private $ptyMode; |
||||
/** @var bool */ |
||||
private $disableOutput; |
||||
|
||||
public function __construct($ttyMode, $ptyMode, $input, $disableOutput) |
||||
{ |
||||
$this->ttyMode = (bool) $ttyMode; |
||||
$this->ptyMode = (bool) $ptyMode; |
||||
$this->disableOutput = (bool) $disableOutput; |
||||
|
||||
if (is_resource($input)) { |
||||
$this->input = $input; |
||||
} else { |
||||
$this->inputBuffer = (string) $input; |
||||
} |
||||
} |
||||
|
||||
public function __destruct() |
||||
{ |
||||
$this->close(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getDescriptors() |
||||
{ |
||||
if ($this->disableOutput) { |
||||
$nullstream = fopen('/dev/null', 'c'); |
||||
|
||||
return array( |
||||
array('pipe', 'r'), |
||||
$nullstream, |
||||
$nullstream, |
||||
); |
||||
} |
||||
|
||||
if ($this->ttyMode) { |
||||
return array( |
||||
array('file', '/dev/tty', 'r'), |
||||
array('file', '/dev/tty', 'w'), |
||||
array('file', '/dev/tty', 'w'), |
||||
); |
||||
} |
||||
|
||||
if ($this->ptyMode && Process::isPtySupported()) { |
||||
return array( |
||||
array('pty'), |
||||
array('pty'), |
||||
array('pty'), |
||||
); |
||||
} |
||||
|
||||
return array( |
||||
array('pipe', 'r'), |
||||
array('pipe', 'w'), // stdout |
||||
array('pipe', 'w'), // stderr |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getFiles() |
||||
{ |
||||
return array(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function readAndWrite($blocking, $close = false) |
||||
{ |
||||
// only stdin is left open, job has been done ! |
||||
// we can now close it |
||||
if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) { |
||||
fclose($this->pipes[0]); |
||||
unset($this->pipes[0]); |
||||
} |
||||
|
||||
if (empty($this->pipes)) { |
||||
return array(); |
||||
} |
||||
|
||||
$this->unblock(); |
||||
|
||||
$read = array(); |
||||
|
||||
if (null !== $this->input) { |
||||
// if input is a resource, let's add it to stream_select argument to |
||||
// fill a buffer |
||||
$r = array_merge($this->pipes, array('input' => $this->input)); |
||||
} else { |
||||
$r = $this->pipes; |
||||
} |
||||
// discard read on stdin |
||||
unset ($r[0]); |
||||
|
||||
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null; |
||||
$e = null; |
||||
|
||||
// let's have a look if something changed in streams |
||||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { |
||||
// if a system call has been interrupted, forget about it, let's try again |
||||
// otherwise, an error occurred, let's reset pipes |
||||
if (!$this->hasSystemCallBeenInterrupted()) { |
||||
$this->pipes = array(); |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
// nothing has changed |
||||
if (0 === $n) { |
||||
return $read; |
||||
} |
||||
|
||||
foreach ($r as $pipe) { |
||||
// prior PHP 5.4 the array passed to stream_select is modified and |
||||
// lose key association, we have to find back the key |
||||
$type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; |
||||
$data = ''; |
||||
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { |
||||
$data .= $dataread; |
||||
} |
||||
|
||||
if ('' !== $data) { |
||||
if ($type === 'input') { |
||||
$this->inputBuffer .= $data; |
||||
} else { |
||||
$read[$type] = $data; |
||||
} |
||||
} |
||||
|
||||
if (false === $data || (true === $close && feof($pipe) && '' === $data)) { |
||||
if ($type === 'input') { |
||||
// no more data to read on input resource |
||||
// use an empty buffer in the next reads |
||||
$this->input = null; |
||||
} else { |
||||
fclose($this->pipes[$type]); |
||||
unset($this->pipes[$type]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (null !== $w && 0 < count($w)) { |
||||
while ($len = strlen($this->inputBuffer)) { |
||||
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k |
||||
if ($written > 0) { |
||||
$this->inputBuffer = (string) substr($this->inputBuffer, $written); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// no input to read on resource, buffer is empty and stdin still open |
||||
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { |
||||
fclose($this->pipes[0]); |
||||
unset($this->pipes[0]); |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function areOpen() |
||||
{ |
||||
return (bool) $this->pipes; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new UnixPipes instance |
||||
* |
||||
* @param Process $process |
||||
* @param string|resource $input |
||||
* |
||||
* @return UnixPipes |
||||
*/ |
||||
public static function create(Process $process, $input) |
||||
{ |
||||
return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); |
||||
} |
||||
} |
@ -0,0 +1,254 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Process\Pipes; |
||||
|
||||
use Symfony\Component\Process\Process; |
||||
use Symfony\Component\Process\Exception\RuntimeException; |
||||
|
||||
/** |
||||
* WindowsPipes implementation uses temporary files as handles. |
||||
* |
||||
* @see https://bugs.php.net/bug.php?id=51800 |
||||
* @see https://bugs.php.net/bug.php?id=65650 |
||||
* |
||||
* @author Romain Neutron <imprec@gmail.com> |
||||
* |
||||
* @internal |
||||
*/ |
||||
class WindowsPipes extends AbstractPipes |
||||
{ |
||||
/** @var array */ |
||||
private $files = array(); |
||||
/** @var array */ |
||||
private $fileHandles = array(); |
||||
/** @var array */ |
||||
private $readBytes = array( |
||||
Process::STDOUT => 0, |
||||
Process::STDERR => 0, |
||||
); |
||||
/** @var bool */ |
||||
private $disableOutput; |
||||
|
||||
public function __construct($disableOutput, $input) |
||||
{ |
||||
$this->disableOutput = (bool) $disableOutput; |
||||
|
||||
if (!$this->disableOutput) { |
||||
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. |
||||
// Workaround for this problem is to use temporary files instead of pipes on Windows platform. |
||||
// |
||||
// @see https://bugs.php.net/bug.php?id=51800 |
||||
$this->files = array( |
||||
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), |
||||
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), |
||||
); |
||||
foreach ($this->files as $offset => $file) { |
||||
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); |
||||
if (false === $this->fileHandles[$offset]) { |
||||
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (is_resource($input)) { |
||||
$this->input = $input; |
||||
} else { |
||||
$this->inputBuffer = $input; |
||||
} |
||||
} |
||||
|
||||
public function __destruct() |
||||
{ |
||||
$this->close(); |
||||
$this->removeFiles(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getDescriptors() |
||||
{ |
||||
if ($this->disableOutput) { |
||||
$nullstream = fopen('NUL', 'c'); |
||||
|
||||
return array( |
||||
array('pipe', 'r'), |
||||
$nullstream, |
||||
$nullstream, |
||||
); |
||||
} |
||||
|
||||
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) |
||||
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 |
||||
// So we redirect output within the commandline and pass the nul device to the process |
||||
return array( |
||||
array('pipe', 'r'), |
||||
array('file', 'NUL', 'w'), |
||||
array('file', 'NUL', 'w'), |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getFiles() |
||||
{ |
||||
return $this->files; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function readAndWrite($blocking, $close = false) |
||||
{ |
||||
$this->write($blocking, $close); |
||||
|
||||
$read = array(); |
||||
$fh = $this->fileHandles; |
||||
foreach ($fh as $type => $fileHandle) { |
||||
if (0 !== fseek($fileHandle, $this->readBytes[$type])) { |
||||
continue; |
||||
} |
||||
$data = ''; |
||||
$dataread = null; |
||||
while (!feof($fileHandle)) { |
||||
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { |
||||
$data .= $dataread; |
||||
} |
||||
} |
||||
if (0 < $length = strlen($data)) { |
||||
$this->readBytes[$type] += $length; |
||||
$read[$type] = $data; |
||||
} |
||||
|
||||
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { |
||||
fclose($this->fileHandles[$type]); |
||||
unset($this->fileHandles[$type]); |
||||
} |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function areOpen() |
||||
{ |
||||
return (bool) $this->pipes && (bool) $this->fileHandles; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function close() |
||||
{ |
||||
parent::close(); |
||||
foreach ($this->fileHandles as $handle) { |
||||
fclose($handle); |
||||
} |
||||
$this->fileHandles = array(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new WindowsPipes instance. |
||||
* |
||||
* @param Process $process The process |
||||
* @param $input |
||||
* |
||||
* @return WindowsPipes |
||||
*/ |
||||
public static function create(Process $process, $input) |
||||
{ |
||||
return new static($process->isOutputDisabled(), $input); |
||||
} |
||||
|
||||
/** |
||||
* Removes temporary files |
||||
*/ |
||||
private function removeFiles() |
||||
{ |
||||
foreach ($this->files as $filename) { |
||||
if (file_exists($filename)) { |
||||
@unlink($filename); |
||||
} |
||||
} |
||||
$this->files = array(); |
||||
} |
||||
|
||||
/** |
||||
* Writes input to stdin |
||||
* |
||||
* @param bool $blocking |
||||
* @param bool $close |
||||
*/ |
||||
private function write($blocking, $close) |
||||
{ |
||||
if (empty($this->pipes)) { |
||||
return; |
||||
} |
||||
|
||||
$this->unblock(); |
||||
|
||||
$r = null !== $this->input ? array('input' => $this->input) : null; |
||||
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null; |
||||
$e = null; |
||||
|
||||
// let's have a look if something changed in streams |
||||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { |
||||
// if a system call has been interrupted, forget about it, let's try again |
||||
// otherwise, an error occurred, let's reset pipes |
||||
if (!$this->hasSystemCallBeenInterrupted()) { |
||||
$this->pipes = array(); |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
// nothing has changed |
||||
if (0 === $n) { |
||||
return; |
||||
} |
||||
|
||||
if (null !== $w && 0 < count($r)) { |
||||
$data = ''; |
||||
while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { |
||||
$data .= $dataread; |
||||
} |
||||
|
||||
$this->inputBuffer .= $data; |
||||
|
||||
if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { |
||||
// no more data to read on input resource |
||||
// use an empty buffer in the next reads |
||||
unset($this->input); |
||||
} |
||||
} |
||||
|
||||
if (null !== $w && 0 < count($w)) { |
||||
while ($len = strlen($this->inputBuffer)) { |
||||
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); |
||||
if ($written > 0) { |
||||
$this->inputBuffer = (string) substr($this->inputBuffer, $written); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// no input to read on resource, buffer is empty and stdin still open |
||||
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { |
||||
fclose($this->pipes[0]); |
||||
unset($this->pipes[0]); |
||||
} |
||||
} |
||||
} |
@ -1,382 +0,0 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Symfony package. |
||||
* |
||||
* (c) Fabien Potencier <fabien@symfony.com> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Symfony\Component\Process; |
||||
|
||||
use Symfony\Component\Process\Exception\RuntimeException; |
||||
|
||||
/** |
||||
* ProcessPipes manages descriptors and pipes for the use of proc_open. |
||||
*/ |
||||
class ProcessPipes |
||||
{ |
||||
/** @var array */ |
||||
public $pipes = array(); |
||||
/** @var array */ |
||||
private $files = array(); |
||||
/** @var array */ |
||||
private $fileHandles = array(); |
||||
/** @var array */ |
||||
private $readBytes = array(); |
||||
/** @var bool */ |
||||
private $useFiles; |
||||
/** @var bool */ |
||||
private $ttyMode; |
||||
/** @var bool */ |
||||
private $ptyMode; |
||||
/** @var bool */ |
||||
private $disableOutput; |
||||
|
||||
const CHUNK_SIZE = 16384; |
||||
|
||||
public function __construct($useFiles, $ttyMode, $ptyMode = false, $disableOutput = false) |
||||
{ |
||||
$this->useFiles = (bool) $useFiles; |
||||
$this->ttyMode = (bool) $ttyMode; |
||||
$this->ptyMode = (bool) $ptyMode; |
||||
$this->disableOutput = (bool) $disableOutput; |
||||
|
||||
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. |
||||
// Workaround for this problem is to use temporary files instead of pipes on Windows platform. |
||||
// |
||||
// @see https://bugs.php.net/bug.php?id=51800 |
||||
if ($this->useFiles && !$this->disableOutput) { |
||||
$this->files = array( |
||||
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), |
||||
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), |
||||
); |
||||
foreach ($this->files as $offset => $file) { |
||||
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); |
||||
if (false === $this->fileHandles[$offset]) { |
||||
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); |
||||
} |
||||
} |
||||
$this->readBytes = array( |
||||
Process::STDOUT => 0, |
||||
Process::STDERR => 0, |
||||
); |
||||
} |
||||
} |
||||
|
||||
public function __destruct() |
||||
{ |
||||
$this->close(); |
||||
$this->removeFiles(); |
||||
} |
||||
|
||||
/** |
||||
* Sets non-blocking mode on pipes. |
||||
*/ |
||||
public function unblock() |
||||
{ |
||||
foreach ($this->pipes as $pipe) { |
||||
stream_set_blocking($pipe, 0); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Closes file handles and pipes. |
||||
*/ |
||||
public function close() |
||||
{ |
||||
$this->closeUnixPipes(); |
||||
foreach ($this->fileHandles as $handle) { |
||||
fclose($handle); |
||||
} |
||||
$this->fileHandles = array(); |
||||
} |
||||
|
||||
/** |
||||
* Closes Unix pipes. |
||||
* |
||||
* Nothing happens in case file handles are used. |
||||
*/ |
||||
public function closeUnixPipes() |
||||
{ |
||||
foreach ($this->pipes as $pipe) { |
||||
fclose($pipe); |
||||
} |
||||
$this->pipes = array(); |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of descriptors for the use of proc_open. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getDescriptors() |
||||
{ |
||||
if ($this->disableOutput) { |
||||
$nullstream = fopen(defined('PHP_WINDOWS_VERSION_BUILD') ? 'NUL' : '/dev/null', 'c'); |
||||
|
||||
return array( |
||||
array('pipe', 'r'), |
||||
$nullstream, |
||||
$nullstream, |
||||
); |
||||
} |
||||
|
||||
if ($this->useFiles) { |
||||
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) |
||||
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 |
||||
// So we redirect output within the commandline and pass the nul device to the process |
||||
return array( |
||||
array('pipe', 'r'), |
||||
array('file', 'NUL', 'w'), |
||||
array('file', 'NUL', 'w'), |
||||
); |
||||
} |
||||
|
||||
if ($this->ttyMode) { |
||||
return array( |
||||
array('file', '/dev/tty', 'r'), |
||||
array('file', '/dev/tty', 'w'), |
||||
array('file', '/dev/tty', 'w'), |
||||
); |
||||
} elseif ($this->ptyMode && Process::isPtySupported()) { |
||||
return array( |
||||
array('pty'), |
||||
array('pty'), |
||||
array('pty'), |
||||
); |
||||
} |
||||
|
||||
return array( |
||||
array('pipe', 'r'), // stdin |
||||
array('pipe', 'w'), // stdout |
||||
array('pipe', 'w'), // stderr |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getFiles() |
||||
{ |
||||
if ($this->useFiles) { |
||||
return $this->files; |
||||
} |
||||
|
||||
return array(); |
||||
} |
||||
|
||||
/** |
||||
* Reads data in file handles and pipes. |
||||
* |
||||
* @param bool $blocking Whether to use blocking calls or not. |
||||
* |
||||
* @return array An array of read data indexed by their fd. |
||||
*/ |
||||
public function read($blocking) |
||||
{ |
||||
return array_replace($this->readStreams($blocking), $this->readFileHandles()); |
||||
} |
||||
|
||||
/** |
||||
* Reads data in file handles and pipes, closes them if EOF is reached. |
||||
* |
||||
* @param bool $blocking Whether to use blocking calls or not. |
||||
* |
||||
* @return array An array of read data indexed by their fd. |
||||
*/ |
||||
public function readAndCloseHandles($blocking) |
||||
{ |
||||
return array_replace($this->readStreams($blocking, true), $this->readFileHandles(true)); |
||||
} |
||||
|
||||
/** |
||||
* Returns if the current state has open file handles or pipes. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function hasOpenHandles() |
||||
{ |
||||
if (!$this->useFiles) { |
||||
return (bool) $this->pipes; |
||||
} |
||||
|
||||
return (bool) $this->pipes && (bool) $this->fileHandles; |
||||
} |
||||
|
||||
/** |
||||
* Writes stdin data. |
||||
* |
||||
* @param bool $blocking Whether to use blocking calls or not. |
||||
* @param string|null $stdin The data to write. |
||||
*/ |
||||
public function write($blocking, $stdin) |
||||
{ |
||||
if (null === $stdin) { |
||||
fclose($this->pipes[0]); |
||||
unset($this->pipes[0]); |
||||
|
||||
return; |
||||
} |
||||
|
||||
$writePipes = array($this->pipes[0]); |
||||
unset($this->pipes[0]); |
||||
$stdinLen = strlen($stdin); |
||||
$stdinOffset = 0; |
||||
|
||||
while ($writePipes) { |
||||
$r = null; |
||||
$w = $writePipes; |
||||
$e = null; |
||||
|
||||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) { |
||||
// if a system call has been interrupted, forget about it, let's try again |
||||
if ($this->hasSystemCallBeenInterrupted()) { |
||||
continue; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
// nothing has changed, let's wait until the process is ready |
||||
if (0 === $n) { |
||||
continue; |
||||
} |
||||
|
||||
if ($w) { |
||||
$written = fwrite($writePipes[0], (binary) substr($stdin, $stdinOffset), 8192); |
||||
if (false !== $written) { |
||||
$stdinOffset += $written; |
||||
} |
||||
if ($stdinOffset >= $stdinLen) { |
||||
fclose($writePipes[0]); |
||||
$writePipes = null; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Reads data in file handles. |
||||
* |
||||
* @param bool $close Whether to close file handles or not. |
||||
* |
||||
* @return array An array of read data indexed by their fd. |
||||
*/ |
||||
private function readFileHandles($close = false) |
||||
{ |
||||
$read = array(); |
||||
$fh = $this->fileHandles; |
||||
foreach ($fh as $type => $fileHandle) { |
||||
if (0 !== fseek($fileHandle, $this->readBytes[$type])) { |
||||
continue; |
||||
} |
||||
$data = ''; |
||||
$dataread = null; |
||||
while (!feof($fileHandle)) { |
||||
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { |
||||
$data .= $dataread; |
||||
} |
||||
} |
||||
if (0 < $length = strlen($data)) { |
||||
$this->readBytes[$type] += $length; |
||||
$read[$type] = $data; |
||||
} |
||||
|
||||
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { |
||||
fclose($this->fileHandles[$type]); |
||||
unset($this->fileHandles[$type]); |
||||
} |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
/** |
||||
* Reads data in file pipes streams. |
||||
* |
||||
* @param bool $blocking Whether to use blocking calls or not. |
||||
* @param bool $close Whether to close file handles or not. |
||||
* |
||||
* @return array An array of read data indexed by their fd. |
||||
*/ |
||||
private function readStreams($blocking, $close = false) |
||||
{ |
||||
if (empty($this->pipes)) { |
||||
usleep(Process::TIMEOUT_PRECISION * 1E4); |
||||
|
||||
return array(); |
||||
} |
||||
|
||||
$read = array(); |
||||
|
||||
$r = $this->pipes; |
||||
$w = null; |
||||
$e = null; |
||||
|
||||
// let's have a look if something changed in streams |
||||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(Process::TIMEOUT_PRECISION * 1E6) : 0)) { |
||||
// if a system call has been interrupted, forget about it, let's try again |
||||
// otherwise, an error occurred, let's reset pipes |
||||
if (!$this->hasSystemCallBeenInterrupted()) { |
||||
$this->pipes = array(); |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
// nothing has changed |
||||
if (0 === $n) { |
||||
return $read; |
||||
} |
||||
|
||||
foreach ($r as $pipe) { |
||||
$type = array_search($pipe, $this->pipes); |
||||
|
||||
$data = ''; |
||||
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { |
||||
$data .= $dataread; |
||||
} |
||||
|
||||
if ('' !== $data) { |
||||
$read[$type] = $data; |
||||
} |
||||
|
||||
if (false === $data || (true === $close && feof($pipe) && '' === $data)) { |
||||
fclose($this->pipes[$type]); |
||||
unset($this->pipes[$type]); |
||||
} |
||||
} |
||||
|
||||
return $read; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if a system call has been interrupted. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
private function hasSystemCallBeenInterrupted() |
||||
{ |
||||
$lastError = error_get_last(); |
||||
|
||||
// stream_select returns false when the `select` system call is interrupted by an incoming signal |
||||
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); |
||||
} |
||||
|
||||
/** |
||||
* Removes temporary files |
||||
*/ |
||||
private function removeFiles() |
||||
{ |
||||
foreach ($this->files as $filename) { |
||||
if (file_exists($filename)) { |
||||
@unlink($filename); |
||||
} |
||||
} |
||||
$this->files = array(); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue