diff --git a/.travis.yml b/.travis.yml index 068383512c..7b5413d8c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 5.3 - 5.4 - 5.5 + - 5.6 - hhvm matrix: diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index 0a8bd9ab67..f9c3b49256 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -100,7 +100,11 @@ class RedirectController extends ContainerAware $qs = $request->getQueryString(); if ($qs) { - $qs = '?'.$qs; + if (strpos($path, '?') === false) { + $qs = '?'.$qs; + } else { + $qs = '&'.$qs; + } } $port = ''; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index 93cf6f6163..34d1eeac28 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -199,7 +199,36 @@ class RedirectControllerTest extends TestCase $this->assertRedirectUrl($returnValue, $expectedUrl); } - private function createRequestObject($scheme, $host, $port, $baseUrl) + public function pathQueryParamsProvider() + { + return array( + array('http://www.example.com/base/redirect-path', '/redirect-path', ''), + array('http://www.example.com/base/redirect-path?foo=bar', '/redirect-path?foo=bar', ''), + array('http://www.example.com/base/redirect-path?foo=bar', '/redirect-path', 'foo=bar'), + array('http://www.example.com/base/redirect-path?foo=bar&abc=example', '/redirect-path?foo=bar', 'abc=example'), + array('http://www.example.com/base/redirect-path?foo=bar&abc=example&baz=def', '/redirect-path?foo=bar', 'abc=example&baz=def'), + ); + } + + /** + * @dataProvider pathQueryParamsProvider + */ + public function testPathQueryParams($expectedUrl, $path, $queryString) + { + $scheme = 'http'; + $host = 'www.example.com'; + $baseUrl = '/base'; + $port = 80; + + $request = $this->createRequestObject($scheme, $host, $port, $baseUrl, $queryString); + + $controller = $this->createRedirectController(); + + $returnValue = $controller->urlRedirectAction($request, $path, false, $scheme, $port, null); + $this->assertRedirectUrl($returnValue, $expectedUrl); + } + + private function createRequestObject($scheme, $host, $port, $baseUrl, $queryString = '') { $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); $request @@ -218,6 +247,10 @@ class RedirectControllerTest extends TestCase ->expects($this->any()) ->method('getBaseUrl') ->will($this->returnValue($baseUrl)); + $request + ->expects($this->any()) + ->method('getQueryString') + ->will($this->returnValue($queryString)); return $request; } diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php index 05544d0c81..2e8f507b48 100644 --- a/src/Symfony/Component/Finder/Expression/Regex.php +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -65,7 +65,11 @@ class Regex implements ValueInterface $start = substr($m[1], 0, 1); $end = substr($m[1], -1); - if (($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { + if ( + ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) + || ($start === '{' && $end === '}') + || ($start === '(' && $end === ')') + ) { return new self(substr($m[1], 1, -1), $m[2], $end); } } diff --git a/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php index 38a522a3e9..5489d6db88 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php @@ -99,21 +99,19 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface */ protected function getMongo() { - if ($this->mongo === null) { - if (preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $this->dsn, $matches)) { - $server = $matches[1].(!empty($matches[2]) ? '/'.$matches[2] : ''); - $database = $matches[2]; - $collection = $matches[3]; - - $mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient'; - $mongo = new $mongoClass($server); - $this->mongo = $mongo->selectCollection($database, $collection); - } else { - throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn)); - } + if (null !== $this->mongo) { + return $this->mongo; } - return $this->mongo; + if (!$parsedDsn = $this->parseDsn($this->dsn)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn)); + } + + list($server, $database, $collection) = $parsedDsn; + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? '\Mongo' : '\MongoClient'; + $mongo = new $mongoClass($server); + + return $this->mongo = $mongo->selectCollection($database, $collection); } /** @@ -233,4 +231,27 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface return $profile; } + + /** + * @param string $dsn + * + * @return null|array Array($server, $database, $collection) + */ + private function parseDsn($dsn) + { + if (!preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $dsn, $matches)) { + return; + } + + $server = $matches[1]; + $database = $matches[2]; + $collection = $matches[3]; + preg_match('#^mongodb://(([^:]+):?(.*)(?=@))?@?([^/]*)(.*)$#', $server, $matchesServer); + + if ('' == $matchesServer[5] && '' != $matches[2]) { + $server .= '/'.$matches[2]; + } + + return array($server, $database, $collection); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php index b63b84c0fb..15fe98695f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php @@ -71,6 +71,32 @@ class MongoDbProfilerStorageTest extends AbstractProfilerStorageTest } } + public function getDsns() + { + return array( + array('mongodb://localhost/symfony_tests/profiler_data', array( + 'mongodb://localhost/symfony_tests', + 'symfony_tests', + 'profiler_data' + )), + array('mongodb://user:password@localhost/symfony_tests/profiler_data', array( + 'mongodb://user:password@localhost/symfony_tests', + 'symfony_tests', + 'profiler_data' + )), + array('mongodb://user:password@localhost/admin/symfony_tests/profiler_data', array( + 'mongodb://user:password@localhost/admin', + 'symfony_tests', + 'profiler_data' + )), + array('mongodb://user:password@localhost:27009,localhost:27010/?replicaSet=rs-name&authSource=admin/symfony_tests/profiler_data', array( + 'mongodb://user:password@localhost:27009,localhost:27010/?replicaSet=rs-name&authSource=admin', + 'symfony_tests', + 'profiler_data' + )) + ); + } + public function testCleanup() { $dt = new \DateTime('-2 day'); @@ -87,6 +113,17 @@ class MongoDbProfilerStorageTest extends AbstractProfilerStorageTest self::$storage->purge(); } + /** + * @dataProvider getDsns + */ + public function testDsnParser($dsn, $expected) + { + $m = new \ReflectionMethod(self::$storage, 'parseDsn'); + $m->setAccessible(true); + + $this->assertEquals($expected, $m->invoke(self::$storage, $dsn)); + } + public function testUtf8() { $profile = new Profile('utf8_test_profile'); diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index fd16c0e0a9..f5104164bf 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -911,7 +911,9 @@ class Process public function setEnv(array $env) { // Process can not handle env values that are arrays - $env = array_filter($env, function ($value) { if (!is_array($value)) { return true; } }); + $env = array_filter($env, function ($value) { + return !is_array($value); + }); $this->env = array(); foreach ($env as $key => $value) { @@ -1190,6 +1192,7 @@ class Process * Reads pipes, executes callback. * * @param Boolean $blocking Whether to use blocking calls or not. + * @param Boolean $close Whether to close file handles or not. */ private function readPipes($blocking, $close) { diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php index 874295b92b..fa107d8c03 100644 --- a/src/Symfony/Component/Process/ProcessPipes.php +++ b/src/Symfony/Component/Process/ProcessPipes.php @@ -79,7 +79,7 @@ class ProcessPipes public function close() { $this->closeUnixPipes(); - foreach ($this->fileHandles as $offset => $handle) { + foreach ($this->fileHandles as $handle) { fclose($handle); } $this->fileHandles = array(); @@ -227,6 +227,8 @@ class ProcessPipes /** * Reads data in file handles. * + * @param Boolean $close Whether to close file handles or not. + * * @return array An array of read data indexed by their fd. */ private function readFileHandles($close = false) @@ -262,6 +264,7 @@ class ProcessPipes * Reads data in file pipes streams. * * @param Boolean $blocking Whether to use blocking calls or not. + * @param Boolean $close Whether to close file handles or not. * * @return array An array of read data indexed by their fd. */ diff --git a/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php index 9a3ecdb68a..5a0f12261c 100644 --- a/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php @@ -29,6 +29,7 @@ class BCryptPasswordEncoder extends BasePasswordEncoder * * @param integer $cost The algorithmic cost that should be used * + * @throws \RuntimeException When no BCrypt encoder is available * @throws \InvalidArgumentException if cost is out of range */ public function __construct($cost) diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 45efced3c8..7a8f051e64 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -386,51 +386,55 @@ class Inline private static function evaluateScalar($scalar) { $scalar = trim($scalar); - + $scalarLower = strtolower($scalar); switch (true) { - case 'null' == strtolower($scalar): + case 'null' == $scalarLower: case '' == $scalar: case '~' == $scalar: return null; - case 0 === strpos($scalar, '!str'): - return (string) substr($scalar, 5); - case 0 === strpos($scalar, '! '): - return intval(self::parseScalar(substr($scalar, 2))); - case 0 === strpos($scalar, '!!php/object:'): - if (self::$objectSupport) { - return unserialize(substr($scalar, 13)); - } - - if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.'); - } - - return null; - case ctype_digit($scalar): - $raw = $scalar; - $cast = intval($scalar); - - return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); - case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): - $raw = $scalar; - $cast = intval($scalar); - - return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); - case 'true' === strtolower($scalar): + case 'true' === $scalarLower: return true; - case 'false' === strtolower($scalar): + case 'false' === $scalarLower: return false; - case is_numeric($scalar): - return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); - case 0 == strcasecmp($scalar, '.inf'): - case 0 == strcasecmp($scalar, '.NaN'): - return -log(0); - case 0 == strcasecmp($scalar, '-.inf'): - return log(0); - case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): - return floatval(str_replace(',', '', $scalar)); - case preg_match(self::getTimestampRegex(), $scalar): - return strtotime($scalar); + // Optimise for returning strings. + case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): + switch (true) { + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return intval(self::parseScalar(substr($scalar, 2))); + case 0 === strpos($scalar, '!!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 13)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return null; + case ctype_digit($scalar): + $raw = $scalar; + $cast = intval($scalar); + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = intval($scalar); + + return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); + case 0 == strcasecmp($scalar, '.inf'): + case 0 == strcasecmp($scalar, '.NaN'): + return -log(0); + case 0 == strcasecmp($scalar, '-.inf'): + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return floatval(str_replace(',', '', $scalar)); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + } default: return (string) $scalar; } diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index e8fe91848e..36aa9b0ace 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -312,7 +312,9 @@ class Parser $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); while ($this->moveToNextLine()) { - if ($this->getCurrentLineIndentation() === $newIndent) { + $indent = $this->getCurrentLineIndentation(); + + if ($indent === $newIndent) { $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); } @@ -321,20 +323,16 @@ class Parser break; } - if ($removeComments && $this->isCurrentLineEmpty() || $this->isCurrentLineBlank()) { - if ($this->isCurrentLineBlank()) { - $data[] = substr($this->currentLine, $newIndent); - } - + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); continue; } - $indent = $this->getCurrentLineIndentation(); + if ($removeComments && $this->isCurrentLineComment()) { + continue; + } - if (preg_match('#^(?P *)$#', $this->currentLine, $match)) { - // empty line - $data[] = $match['text']; - } elseif ($indent >= $newIndent) { + if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } elseif (0 == $indent) { $this->moveToPreviousLine();