Merge branch '2.2' into 2.3 (closes #8955)

* 2.2:
  [HttpFoundation] removed extra parenthesis
  [Process][2.2] Fix Process component on windows
  [HttpFoundation] improve perf of previous merge (refs #8882)
  Request->getPort() should prefer HTTP_HOST over SERVER_PORT
  Fixing broken http auth digest in some circumstances (php-fpm + apache).
  fixed typo

Conflicts:
	src/Symfony/Component/Process/Process.php
This commit is contained in:
Fabien Potencier 2013-09-07 18:29:51 +02:00
commit 3689849e8b
9 changed files with 350 additions and 245 deletions

View File

@ -295,7 +295,7 @@ class DateTypeTest extends TypeTestCase
} }
/** /**
* This test is to check that the strings '0', '1', '2', '3' are no accepted * This test is to check that the strings '0', '1', '2', '3' are not accepted
* as valid IntlDateFormatter constants for FULL, LONG, MEDIUM or SHORT respectively. * as valid IntlDateFormatter constants for FULL, LONG, MEDIUM or SHORT respectively.
* *
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException

View File

@ -905,6 +905,14 @@ class Request
} }
} }
if ($host = $this->headers->get('HOST')) {
if (false !== $pos = strrpos($host, ':')) {
return intval(substr($host, $pos + 1));
}
return 'https' === $this->getScheme() ? 443 : 80;
}
return $this->server->get('SERVER_PORT'); return $this->server->get('SERVER_PORT');
} }
@ -1613,7 +1621,7 @@ class Request
$seg = $segs[$index]; $seg = $segs[$index];
$baseUrl = '/'.$seg.$baseUrl; $baseUrl = '/'.$seg.$baseUrl;
++$index; ++$index;
} while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
} }
// Does the baseUrl have anything in common with the request_uri? // Does the baseUrl have anything in common with the request_uri?
@ -1630,7 +1638,7 @@ class Request
} }
$truncatedRequestUri = $requestUri; $truncatedRequestUri = $requestUri;
if (($pos = strpos($requestUri, '?')) !== false) { if (false !== $pos = strpos($requestUri, '?')) {
$truncatedRequestUri = substr($requestUri, 0, $pos); $truncatedRequestUri = substr($requestUri, 0, $pos);
} }
@ -1643,7 +1651,7 @@ class Request
// If using mod_rewrite or ISAPI_Rewrite strip the script filename // If using mod_rewrite or ISAPI_Rewrite strip the script filename
// out of baseUrl. $pos !== 0 makes sure it is not matching a value // out of baseUrl. $pos !== 0 makes sure it is not matching a value
// from PATH_INFO or QUERY_STRING // from PATH_INFO or QUERY_STRING
if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) { if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
} }
@ -1696,7 +1704,7 @@ class Request
$requestUri = substr($requestUri, 0, $pos); $requestUri = substr($requestUri, 0, $pos);
} }
if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl))))) { if (null !== $baseUrl && false === $pathInfo = substr($requestUri, strlen($baseUrl))) {
// If substr() returns false then PATH_INFO is set to an empty string // If substr() returns false then PATH_INFO is set to an empty string
return '/'; return '/';
} elseif (null === $baseUrl) { } elseif (null === $baseUrl) {

View File

@ -64,11 +64,17 @@ class ServerBag extends ParameterBag
$authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
} }
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic if (null !== $authorizationHeader) {
if ((null !== $authorizationHeader) && (0 === stripos($authorizationHeader, 'basic'))) { if (0 === stripos($authorizationHeader, 'basic')) {
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6))); // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
if (count($exploded) == 2) { $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; if (count($exploded) == 2) {
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
}
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest'))) {
// In some circumstances PHP_AUTH_DIGEST needs to be set
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
} }
} }
} }
@ -76,6 +82,8 @@ class ServerBag extends ParameterBag
// PHP_AUTH_USER/PHP_AUTH_PW // PHP_AUTH_USER/PHP_AUTH_PW
if (isset($headers['PHP_AUTH_USER'])) { if (isset($headers['PHP_AUTH_USER'])) {
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
} }
return $headers; return $headers;

View File

@ -1526,6 +1526,18 @@ class RequestTest extends \PHPUnit_Framework_TestCase
// trusted hosts // trusted hosts
$request->headers->set('host', 'trusted.com'); $request->headers->set('host', 'trusted.com');
$this->assertEquals('trusted.com', $request->getHost()); $this->assertEquals('trusted.com', $request->getHost());
$this->assertEquals(80, $request->getPort());
$request->server->set('HTTPS', true);
$request->headers->set('host', 'trusted.com');
$this->assertEquals('trusted.com', $request->getHost());
$this->assertEquals(443, $request->getPort());
$request->server->set('HTTPS', false);
$request->headers->set('host', 'trusted.com:8000');
$this->assertEquals('trusted.com', $request->getHost());
$this->assertEquals(8000, $request->getPort());
$request->headers->set('host', 'subdomain.trusted.com'); $request->headers->set('host', 'subdomain.trusted.com');
$this->assertEquals('subdomain.trusted.com', $request->getHost()); $this->assertEquals('subdomain.trusted.com', $request->getHost());

View File

@ -89,6 +89,28 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase
), $bag->getHeaders()); ), $bag->getHeaders());
} }
public function testHttpDigestAuthWithPhpCgi()
{
$digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
$bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
$this->assertEquals(array(
'AUTHORIZATION' => $digest,
'PHP_AUTH_DIGEST' => $digest,
), $bag->getHeaders());
}
public function testHttpDigestAuthWithPhpCgiRedirect()
{
$digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
$bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $digest));
$this->assertEquals(array(
'AUTHORIZATION' => $digest,
'PHP_AUTH_DIGEST' => $digest,
), $bag->getHeaders());
}
public function testOAuthBearerAuth() public function testOAuthBearerAuth()
{ {
$headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';

View File

@ -54,15 +54,14 @@ class Process
private $stderr; private $stderr;
private $enhanceWindowsCompatibility; private $enhanceWindowsCompatibility;
private $enhanceSigchildCompatibility; private $enhanceSigchildCompatibility;
private $pipes;
private $process; private $process;
private $status = self::STATUS_READY; private $status = self::STATUS_READY;
private $incrementalOutputOffset; private $incrementalOutputOffset;
private $incrementalErrorOutputOffset; private $incrementalErrorOutputOffset;
private $tty; private $tty;
private $fileHandles; private $useFileHandles = false;
private $readBytes; private $processPipes;
private static $sigchild; private static $sigchild;
@ -154,6 +153,7 @@ class Process
} }
$this->stdin = $stdin; $this->stdin = $stdin;
$this->setTimeout($timeout); $this->setTimeout($timeout);
$this->useFileHandles = defined('PHP_WINDOWS_VERSION_BUILD');
$this->enhanceWindowsCompatibility = true; $this->enhanceWindowsCompatibility = true;
$this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled(); $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled();
$this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
@ -237,18 +237,15 @@ class Process
} }
} }
$this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options); $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
if (!is_resource($this->process)) { if (!is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.'); throw new RuntimeException('Unable to launch a new process.');
} }
$this->status = self::STATUS_STARTED; $this->status = self::STATUS_STARTED;
foreach ($this->pipes as $pipe) { $this->processPipes->unblock();
stream_set_blocking($pipe, false); $this->processPipes->write(false, $this->stdin);
}
$this->writePipes();
$this->updateStatus(false); $this->updateStatus(false);
$this->checkTimeout(); $this->checkTimeout();
} }
@ -300,24 +297,12 @@ class Process
if (null !== $callback) { if (null !== $callback) {
$this->callback = $this->buildCallback($callback); $this->callback = $this->buildCallback($callback);
} }
while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) { while ($this->processInformation['running']) {
$this->checkTimeout(); $this->checkTimeout();
$this->readPipes(true); $this->updateStatus(true);
} }
$this->updateStatus(false); $this->updateStatus(false);
if ($this->processInformation['signaled']) { $this->processPipes->close();
if ($this->isSigchildEnabled()) {
throw new RuntimeException('The process has been signaled.');
}
throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
}
$time = 0;
while ($this->isRunning() && $time < 1000000) {
$time += 1000;
usleep(1000);
}
if ($this->processInformation['signaled']) { if ($this->processInformation['signaled']) {
if ($this->isSigchildEnabled()) { if ($this->isSigchildEnabled()) {
@ -951,38 +936,10 @@ class Process
*/ */
private function getDescriptors() private function getDescriptors()
{ {
//Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. $this->processPipes = new ProcessPipes($this->useFileHandles);
//Workaround for this problem is to use temporary files instead of pipes on Windows platform. $descriptors = $this->processPipes->getDescriptors();
//@see https://bugs.php.net/bug.php?id=51800
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->fileHandles = array(
self::STDOUT => tmpfile(),
);
if (false === $this->fileHandles[self::STDOUT]) {
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(
self::STDOUT => 0,
);
return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w')); if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
}
if ($this->tty) {
$descriptors = array(
array('file', '/dev/tty', 'r'),
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
} else {
$descriptors = array(
array('pipe', 'r'), // stdin
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors = array_merge($descriptors, array(array('pipe', 'w'))); $descriptors = array_merge($descriptors, array(array('pipe', 'w')));
@ -1060,41 +1017,6 @@ class Process
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
} }
/**
* Handles the windows file handles fallbacks.
*
* @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
*/
private function processFileHandles($closeEmptyHandles = false)
{
$fh = $this->fileHandles;
foreach ($fh as $type => $fileHandle) {
fseek($fileHandle, $this->readBytes[$type]);
$data = fread($fileHandle, 8192);
if (strlen($data) > 0) {
$this->readBytes[$type] += strlen($data);
call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
}
if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
fclose($fileHandle);
unset($this->fileHandles[$type]);
}
}
}
/**
* Returns true if a system call has been interrupted.
*
* @return Boolean
*/
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');
}
/** /**
* Reads pipes, executes callback. * Reads pipes, executes callback.
* *
@ -1102,119 +1024,11 @@ class Process
*/ */
private function readPipes($blocking) private function readPipes($blocking)
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) { foreach ($this->processPipes->read($blocking) as $type => $data) {
$this->processFileHandles(!$this->pipes); if (3 == $type) {
} $this->fallbackExitcode = (int) $data;
} else {
if ($this->pipes) { call_user_func($this->callback, $type === self::STDOUT ? self::OUT : self::ERR, $data);
$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(self::TIMEOUT_PRECISION * 1E6) : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occured, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return;
}
// nothing has changed
if (0 === $n) {
return;
}
$this->processReadPipes($r);
}
}
/**
* Writes data to pipes.
*
* @param Boolean $blocking Whether to use blocking calls or not.
*/
private function writePipes()
{
if ($this->tty) {
$this->status = self::STATUS_TERMINATED;
return;
}
if (null === $this->stdin) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
return;
}
$writePipes = array($this->pipes[0]);
unset($this->pipes[0]);
$stdinLen = strlen($this->stdin);
$stdinOffset = 0;
while ($writePipes) {
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->processFileHandles();
}
$r = $this->pipes;
$w = $writePipes;
$e = null;
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::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($this->stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
$this->processReadPipes($r);
}
}
/**
* Processes read pipes, executes callback on it.
*
* @param array $pipes
*/
private function processReadPipes(array $pipes)
{
foreach ($pipes as $pipe) {
$type = array_search($pipe, $this->pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
// last exit code is output and caught to work around --enable-sigchild
if (3 == $type) {
$this->fallbackExitcode = (int) $data;
} else {
call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
}
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
} }
} }
} }
@ -1237,11 +1051,7 @@ class Process
*/ */
private function close() private function close()
{ {
foreach ($this->pipes as $pipe) { $this->processPipes->close();
fclose($pipe);
}
$this->pipes = null;
$exitcode = -1; $exitcode = -1;
if (is_resource($this->process)) { if (is_resource($this->process)) {
@ -1258,13 +1068,6 @@ class Process
$this->exitcode = 128 + $this->processInformation['termsig']; $this->exitcode = 128 + $this->processInformation['termsig'];
} }
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
foreach ($this->fileHandles as $fileHandle) {
fclose($fileHandle);
}
$this->fileHandles = array();
}
return $this->exitcode; return $this->exitcode;
} }
@ -1280,11 +1083,8 @@ class Process
$this->processInformation = null; $this->processInformation = null;
$this->stdout = null; $this->stdout = null;
$this->stderr = null; $this->stderr = null;
$this->pipes = null;
$this->process = null; $this->process = null;
$this->status = self::STATUS_READY; $this->status = self::STATUS_READY;
$this->fileHandles = null;
$this->readBytes = null;
$this->incrementalOutputOffset = 0; $this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0; $this->incrementalErrorOutputOffset = 0;
} }

View File

@ -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;
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 $fileHandles = array();
/** @var array */
private $readBytes = array();
/** @var Boolean */
private $useFiles;
public function __construct($useFiles = false)
{
$this->useFiles = (Boolean) $useFiles;
// 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.
//
// Please note that this work around prevents hanging but
// another issue occurs : In some race conditions, some data may be
// lost or corrupted.
//
// @see https://bugs.php.net/bug.php?id=51800
if ($this->useFiles) {
$this->fileHandles = array(
Process::STDOUT => tmpfile(),
Process::STDERR => tmpfile(),
);
if (false === $this->fileHandles[Process::STDOUT]) {
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 (false === $this->fileHandles[Process::STDERR]) {
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();
}
/**
* 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()
{
foreach ($this->pipes as $offset => $pipe) {
fclose($pipe);
}
foreach ($this->fileHandles as $offset => $handle) {
fclose($handle);
}
$this->fileHandles = $this->pipes = array();
}
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors()
{
if ($this->useFiles) {
return array(
array('pipe', 'r'),
$this->fileHandles[Process::STDOUT],
$this->fileHandles[Process::STDERR],
);
}
return array(
array('pipe', 'r'), // stdin
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
/**
* Reads data in file handles and pipes.
*
* @param Boolean $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());
}
/**
* Writes stdin data.
*
* @param Boolean $blocking Whether to use blocking calls or not.
* @param string $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.
*
* @return array An array of read data indexed by their fd.
*/
private function readFileHandles()
{
$read = array();
foreach ($this->fileHandles as $type => $fileHandle) {
fseek($fileHandle, $this->readBytes[$type]);
$data = '';
while (!feof($fileHandle)) {
$data .= fread($fileHandle, 8192);
}
if (0 < $length = strlen($data)) {
$this->readBytes[$type] += $length;
$read[$type] = $data;
}
}
return $read;
}
/**
* Reads data in file pipes streams.
*
* @param Boolean $blocking Whether to use blocking calls or not.
*
* @return array An array of read data indexed by their fd.
*/
private function readStreams($blocking)
{
$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 occured, 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 = fread($pipe, 8192);
if (strlen($data) > 0) {
$read[$type] = $data;
}
}
return $read;
}
/**
* Returns true if a system call has been interrupted.
*
* @return Boolean
*/
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');
}
}

View File

@ -69,17 +69,16 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{ {
$data = ''; $data = '';
$process = $this->getProcess('echo "foo";sleep 1;echo "foo"'); $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo');
$process->start(function ($type, $buffer) use (&$data) { $process->start(function ($type, $buffer) use (&$data) {
$data .= $buffer; $data .= $buffer;
}); });
$start = microtime(true);
while ($process->isRunning()) { while ($process->isRunning()) {
usleep(10000); usleep(10000);
} }
$this->assertEquals("foo\nfoo\n", $data); $this->assertEquals(2, preg_match_all('/foo/', $data, $matches));
} }
/** /**
@ -119,6 +118,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function chainedCommandsOutputProvider() public function chainedCommandsOutputProvider()
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
return array(
array("2 \r\n2\r\n", '&&', '2')
);
}
return array( return array(
array("1\n1\n", ';', '1'), array("1\n1\n", ';', '1'),
array("2\n2\n", '&&', '2'), array("2\n2\n", '&&', '2'),
@ -131,10 +136,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testChainedCommandsOutput($expected, $operator, $input) public function testChainedCommandsOutput($expected, $operator, $input)
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Does it work on windows ?');
}
$process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input)); $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
$process->run(); $process->run();
$this->assertEquals($expected, $process->getOutput()); $this->assertEquals($expected, $process->getOutput());
@ -173,7 +174,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetOutput() public function testGetOutput()
{ {
$p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}'))); $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++; usleep(500); }')));
$p->run(); $p->run();
$this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
@ -308,7 +309,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIsSuccessfulOnlyAfterTerminated() public function testIsSuccessfulOnlyAfterTerminated()
{ {
$process = $this->getProcess('sleep 1'); $process = $this->getProcess('php -r "sleep(1);"');
$process->start(); $process->start();
while ($process->isRunning()) { while ($process->isRunning()) {
$this->assertFalse($process->isSuccessful()); $this->assertFalse($process->isSuccessful());
@ -441,7 +442,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testRunProcessWithTimeout() public function testRunProcessWithTimeout()
{ {
$timeout = 0.5; $timeout = 0.5;
$process = $this->getProcess('sleep 3'); $process = $this->getProcess('php -r "sleep(3);"');
$process->setTimeout($timeout); $process->setTimeout($timeout);
$start = microtime(true); $start = microtime(true);
try { try {
@ -459,7 +460,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{ {
$timeout = 0.5; $timeout = 0.5;
$precision = 100000; $precision = 100000;
$process = $this->getProcess('sleep 3'); $process = $this->getProcess('php -r "sleep(3);"');
$process->setTimeout($timeout); $process->setTimeout($timeout);
$start = microtime(true); $start = microtime(true);

View File

@ -8,9 +8,9 @@ define('ERR_WRITE_FAILED', 4);
$read = array(STDIN); $read = array(STDIN);
$write = array(STDOUT, STDERR); $write = array(STDOUT, STDERR);
stream_set_blocking(STDIN, false); stream_set_blocking(STDIN, 0);
stream_set_blocking(STDOUT, false); stream_set_blocking(STDOUT, 0);
stream_set_blocking(STDERR, false); stream_set_blocking(STDERR, 0);
$out = $err = ''; $out = $err = '';
while ($read || $write) { while ($read || $write) {
@ -26,7 +26,7 @@ while ($read || $write) {
} }
if (in_array(STDOUT, $w) && strlen($out) > 0) { if (in_array(STDOUT, $w) && strlen($out) > 0) {
$written = fwrite(STDOUT, (binary) $out, 1024); $written = fwrite(STDOUT, (binary) $out, 32768);
if (false === $written) { if (false === $written) {
die(ERR_WRITE_FAILED); die(ERR_WRITE_FAILED);
} }
@ -37,7 +37,7 @@ while ($read || $write) {
} }
if (in_array(STDERR, $w) && strlen($err) > 0) { if (in_array(STDERR, $w) && strlen($err) > 0) {
$written = fwrite(STDERR, (binary) $err, 1024); $written = fwrite(STDERR, (binary) $err, 32768);
if (false === $written) { if (false === $written) {
die(ERR_WRITE_FAILED); die(ERR_WRITE_FAILED);
} }
@ -48,7 +48,7 @@ while ($read || $write) {
} }
if ($r) { if ($r) {
$str = fread(STDIN, 1024); $str = fread(STDIN, 32768);
if (false !== $str) { if (false !== $str) {
$out .= $str; $out .= $str;
$err .= $str; $err .= $str;