From ac1103f80b2cd64b707401936c3cf12961d882c9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 9 May 2019 09:23:25 +0200 Subject: [PATCH 01/51] updated version to 4.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f30975114..bf73d393c 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } } } From 000820ae107d2f8c9cc54d4171a5658c5a974c43 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 28 May 2019 17:41:12 +0200 Subject: [PATCH 02/51] Allow Symfony 5.0 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index bf73d393c..efc4b9425 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,12 @@ ], "require": { "php": "^7.1.3", - "symfony/mime": "^4.3", + "symfony/mime": "^4.3|^5.0", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/expression-language": "^3.4|^4.0|^5.0" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, From 01d599bc5b401d0dfafb2e0829a4338826219144 Mon Sep 17 00:00:00 2001 From: dFayet Date: Tue, 28 May 2019 18:10:09 +0200 Subject: [PATCH 03/51] [HTTP Foundation] Deprecate passing argument to method Request::isMethodSafe() --- BinaryFileResponse.php | 2 +- CHANGELOG.md | 5 +++++ Request.php | 9 +++------ Tests/RequestTest.php | 12 +----------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php index e21782095..443c02888 100644 --- a/BinaryFileResponse.php +++ b/BinaryFileResponse.php @@ -204,7 +204,7 @@ public function prepare(Request $request) if (!$this->headers->has('Accept-Ranges')) { // Only accept ranges on safe HTTP methods - $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none'); + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); } if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 54acd6ae1..7ecbdffa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + 4.3.0 ----- diff --git a/Request.php b/Request.php index fffe2ab81..83cdf6016 100644 --- a/Request.php +++ b/Request.php @@ -1437,15 +1437,12 @@ public function isMethod($method) * * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 * - * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. - * * @return bool */ - public function isMethodSafe(/* $andCacheable = true */) + public function isMethodSafe() { - if (!\func_num_args() || func_get_arg(0)) { - // setting $andCacheable to false should be deprecated in 4.1 - throw new \BadMethodCallException('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is not supported.'); + if (\func_num_args() > 0) { + @trigger_error(sprintf('Passing arguments to "%s()" has been deprecated since Symfony 4.4; use "%s::isMethodCacheable() to check if the method is cacheable instead."', __METHOD__, __CLASS__), E_USER_DEPRECATED); } return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index ab0dcf681..c1168f5e4 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -2115,7 +2115,7 @@ public function testMethodSafe($method, $safe) { $request = new Request(); $request->setMethod($method); - $this->assertEquals($safe, $request->isMethodSafe(false)); + $this->assertEquals($safe, $request->isMethodSafe()); } public function methodSafeProvider() @@ -2134,16 +2134,6 @@ public function methodSafeProvider() ]; } - /** - * @expectedException \BadMethodCallException - */ - public function testMethodSafeChecksCacheable() - { - $request = new Request(); - $request->setMethod('OPTIONS'); - $request->isMethodSafe(); - } - /** * @dataProvider methodCacheableProvider */ From daa0601bef971bdca35b8b33d01db7720bee77d8 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Mon, 17 Jun 2019 00:17:09 +0100 Subject: [PATCH 04/51] Fine tune constructor types --- RedirectResponse.php | 4 ++-- Tests/RedirectResponseTest.php | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/RedirectResponse.php b/RedirectResponse.php index 8d04aa42c..c6b559beb 100644 --- a/RedirectResponse.php +++ b/RedirectResponse.php @@ -32,7 +32,7 @@ class RedirectResponse extends Response * * @see http://tools.ietf.org/html/rfc2616#section-10.3 */ - public function __construct(?string $url, int $status = 302, array $headers = []) + public function __construct(string $url, int $status = 302, array $headers = []) { parent::__construct('', $status, $headers); @@ -82,7 +82,7 @@ public function getTargetUrl() */ public function setTargetUrl($url) { - if (empty($url)) { + if ('' == $url) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); } diff --git a/Tests/RedirectResponseTest.php b/Tests/RedirectResponseTest.php index 5f6a8ac08..24d703d52 100644 --- a/Tests/RedirectResponseTest.php +++ b/Tests/RedirectResponseTest.php @@ -28,10 +28,11 @@ public function testGenerateMetaRedirect() /** * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot redirect to an empty URL. */ - public function testRedirectResponseConstructorNullUrl() + public function testRedirectResponseConstructorEmptyUrl() { - $response = new RedirectResponse(null); + $response = new RedirectResponse(''); } /** From 38c04ea9fccc0d3f75199e99c8a92ed3644c79c7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 17 Jun 2019 20:33:53 +0200 Subject: [PATCH 05/51] Add BC layer for updated constructor types --- RedirectResponse.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/RedirectResponse.php b/RedirectResponse.php index c6b559beb..1d4f37cae 100644 --- a/RedirectResponse.php +++ b/RedirectResponse.php @@ -32,8 +32,13 @@ class RedirectResponse extends Response * * @see http://tools.ietf.org/html/rfc2616#section-10.3 */ - public function __construct(string $url, int $status = 302, array $headers = []) + public function __construct(?string $url, int $status = 302, array $headers = []) { + if (null === $url) { + @trigger_error(sprintf('Passing a null url when instantiating a "%s" is deprecated since Symfony 4.4.', __CLASS__), E_USER_DEPRECATED); + $url = ''; + } + parent::__construct('', $status, $headers); $this->setTargetUrl($url); @@ -82,7 +87,7 @@ public function getTargetUrl() */ public function setTargetUrl($url) { - if ('' == $url) { + if ('' === ($url ?? '')) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); } From 9b9980bab1e08b1518cbfe3530970254f2e36027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 28 Jun 2019 15:18:47 +0200 Subject: [PATCH 06/51] [HttpFoundation] Deprecated ApacheRequest --- ApacheRequest.php | 4 ++++ CHANGELOG.md | 1 + Tests/ApacheRequestTest.php | 1 + 3 files changed, 6 insertions(+) diff --git a/ApacheRequest.php b/ApacheRequest.php index 4e99186dc..f189cde58 100644 --- a/ApacheRequest.php +++ b/ApacheRequest.php @@ -11,9 +11,13 @@ namespace Symfony\Component\HttpFoundation; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ApacheRequest::class, Request::class), E_USER_DEPRECATED); + /** * Request represents an HTTP request from an Apache server. * + * @deprecated since Symfony 4.4. Use the Request class instead. + * * @author Fabien Potencier */ class ApacheRequest extends Request diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ecbdffa9..29e06e678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. 4.3.0 ----- diff --git a/Tests/ApacheRequestTest.php b/Tests/ApacheRequestTest.php index 6fa3b8891..7a5bd378a 100644 --- a/Tests/ApacheRequestTest.php +++ b/Tests/ApacheRequestTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\ApacheRequest; +/** @group legacy */ class ApacheRequestTest extends TestCase { /** From 703f5084a775c652eb806df0f6c973ae1e9b2c5c Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Tue, 2 Jul 2019 17:48:31 -0400 Subject: [PATCH 07/51] Improving the request/response format autodetection --- Request.php | 23 +++++++++++++++++++++++ Response.php | 2 +- Tests/RequestTest.php | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Request.php b/Request.php index 83cdf6016..a950b0a15 100644 --- a/Request.php +++ b/Request.php @@ -192,6 +192,10 @@ class Request protected static $requestFactory; + /** + * @var string|null + */ + private $preferredFormat; private $isHostValid = true; private $isForwardedValid = true; @@ -1559,6 +1563,25 @@ public function isNoCache() return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat) { + return $this->preferredFormat; + } + + $this->preferredFormat = $this->getRequestFormat($this->getContentType()); + + if (null === $this->preferredFormat) { + foreach ($this->getAcceptableContentTypes() as $contentType) { + if (null !== $this->preferredFormat = $this->getFormat($contentType)) { + break; + } + } + } + + return $this->preferredFormat ?: $default; + } + /** * Returns the preferred language. * diff --git a/Response.php b/Response.php index d1263ca7a..168155849 100644 --- a/Response.php +++ b/Response.php @@ -270,7 +270,7 @@ public function prepare(Request $request) } else { // Content-type based on the Request if (!$headers->has('Content-Type')) { - $format = $request->getRequestFormat(); + $format = $request->getPreferredFormat(); if (null !== $format && $mimeType = $request->getMimeType($format)) { $headers->set('Content-Type', $mimeType); } diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index c1168f5e4..6573da078 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -399,6 +399,32 @@ public function testDuplicateWithFormat() $this->assertEquals('xml', $dup->getRequestFormat()); } + public function testGetPreferredFormat() + { + $request = new Request(); + $this->assertNull($request->getPreferredFormat(null)); + $this->assertSame('html', $request->getPreferredFormat()); + $this->assertSame('json', $request->getPreferredFormat('json')); + + $request->setRequestFormat('atom'); + $request->headers->set('Content-Type', 'application/json'); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('atom', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Content-Type', 'application/json'); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('json', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('xml', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Accept', 'application/json;q=0.8,application/xml;q=0.9'); + $this->assertSame('xml', $request->getPreferredFormat()); + } + /** * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat */ From 732978b1804fa00c86befd8d3808054969d154ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Jul 2019 22:32:34 +0200 Subject: [PATCH 08/51] [HttpFoundation] Accept must take the lead for Request::getPreferredFormat() --- Request.php | 21 ++++++++++++++------- Tests/RequestTest.php | 8 ++++---- Tests/ResponseTest.php | 1 + 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Request.php b/Request.php index a950b0a15..4c5260520 100644 --- a/Request.php +++ b/Request.php @@ -1347,6 +1347,8 @@ public function setFormat($format, $mimeTypes) * * _format request attribute * * $default * + * @see getPreferredFormat + * * @param string|null $default The default format * * @return string|null The request format @@ -1563,22 +1565,27 @@ public function isNoCache() return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat + * * the values of the Accept HTTP header + * * the content type of the body of the request. + */ public function getPreferredFormat(?string $default = 'html'): ?string { if (null !== $this->preferredFormat) { return $this->preferredFormat; } - $this->preferredFormat = $this->getRequestFormat($this->getContentType()); - - if (null === $this->preferredFormat) { - foreach ($this->getAcceptableContentTypes() as $contentType) { - if (null !== $this->preferredFormat = $this->getFormat($contentType)) { - break; - } + $preferredFormat = null; + foreach ($this->getAcceptableContentTypes() as $contentType) { + if ($preferredFormat = $this->getFormat($contentType)) { + break; } } + $this->preferredFormat = $this->getRequestFormat($preferredFormat ?: $this->getContentType()); + return $this->preferredFormat ?: $default; } diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index 6573da078..500d590f7 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -407,14 +407,14 @@ public function testGetPreferredFormat() $this->assertSame('json', $request->getPreferredFormat('json')); $request->setRequestFormat('atom'); - $request->headers->set('Content-Type', 'application/json'); - $request->headers->set('Accept', 'application/xml'); + $request->headers->set('Accept', 'application/ld+json'); + $request->headers->set('Content-Type', 'application/merge-patch+json'); $this->assertSame('atom', $request->getPreferredFormat()); $request = new Request(); - $request->headers->set('Content-Type', 'application/json'); $request->headers->set('Accept', 'application/xml'); - $this->assertSame('json', $request->getPreferredFormat()); + $request->headers->set('Content-Type', 'application/json'); + $this->assertSame('xml', $request->getPreferredFormat()); $request = new Request(); $request->headers->set('Accept', 'application/xml'); diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php index 7856a77c0..550d7ce0b 100644 --- a/Tests/ResponseTest.php +++ b/Tests/ResponseTest.php @@ -504,6 +504,7 @@ public function testPrepareSetContentType() $response = new Response('foo'); $request = Request::create('/'); $request->setRequestFormat('json'); + $request->headers->remove('accept'); $response->prepare($request); From 25b5dd2a2da777de754695f0c30d2c6aa28070e4 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 27 Jul 2019 20:05:40 +0200 Subject: [PATCH 09/51] add parameter type declarations to private methods --- BinaryFileResponse.php | 2 +- Request.php | 4 ++-- Session/Storage/Handler/PdoSessionHandler.php | 20 ++++--------------- Session/Storage/MetadataBag.php | 2 +- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php index 443c02888..79329e2e2 100644 --- a/BinaryFileResponse.php +++ b/BinaryFileResponse.php @@ -269,7 +269,7 @@ public function prepare(Request $request) return $this; } - private function hasValidIfRangeHeader($header) + private function hasValidIfRangeHeader(?string $header) { if ($this->getEtag() === $header) { return true; diff --git a/Request.php b/Request.php index 8da2d77de..3b0dbfbfd 100644 --- a/Request.php +++ b/Request.php @@ -1983,7 +1983,7 @@ public function isFromTrustedProxy() return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); } - private function getTrustedValues($type, $ip = null) + private function getTrustedValues(int $type, string $ip = null) { $clientValues = []; $forwardedValues = []; @@ -2034,7 +2034,7 @@ private function getTrustedValues($type, $ip = null) throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); } - private function normalizeAndFilterClientIps(array $clientIps, $ip) + private function normalizeAndFilterClientIps(array $clientIps, string $ip) { if (!$clientIps) { return []; diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index a3877ef4c..f40d9ec60 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -423,10 +423,8 @@ public function close() /** * Lazy-connects to the database. - * - * @param string $dsn DSN string */ - private function connect($dsn) + private function connect(string $dsn) { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -436,13 +434,11 @@ private function connect($dsn) /** * Builds a PDO DSN from a URL-like connection string. * - * @param string $dsnOrUrl - * * @return string * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ - private function buildDsnFromUrl($dsnOrUrl) + private function buildDsnFromUrl(string $dsnOrUrl) { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); @@ -775,13 +771,9 @@ private function getSelectSql(): string /** * Returns an insert statement supported by the database for writing session data. * - * @param string $sessionId Session ID - * @param string $sessionData Encoded session data - * @param int $maxlifetime session.gc_maxlifetime - * * @return \PDOStatement The insert statement */ - private function getInsertStatement($sessionId, $sessionData, $maxlifetime) + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime) { switch ($this->driver) { case 'oci': @@ -808,13 +800,9 @@ private function getInsertStatement($sessionId, $sessionData, $maxlifetime) /** * Returns an update statement supported by the database for writing session data. * - * @param string $sessionId Session ID - * @param string $sessionData Encoded session data - * @param int $maxlifetime session.gc_maxlifetime - * * @return \PDOStatement The update statement */ - private function getUpdateStatement($sessionId, $sessionData, $maxlifetime) + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime) { switch ($this->driver) { case 'oci': diff --git a/Session/Storage/MetadataBag.php b/Session/Storage/MetadataBag.php index 2eff4109b..ece3ff34f 100644 --- a/Session/Storage/MetadataBag.php +++ b/Session/Storage/MetadataBag.php @@ -159,7 +159,7 @@ public function setName($name) $this->name = $name; } - private function stampCreated($lifetime = null) + private function stampCreated(int $lifetime = null) { $timeStamp = time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; From 64b07b0f881711af922e31f16d38bd06acb9103a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 6 Aug 2019 07:23:17 +0200 Subject: [PATCH 10/51] removed unneeded phpdocs --- Session/Session.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Session/Session.php b/Session/Session.php index db0b9aeb0..3e0e0f6da 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -31,11 +31,6 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable private $data = []; private $usageIndex = 0; - /** - * @param SessionStorageInterface $storage A SessionStorageInterface instance - * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) - * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) - */ public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) { $this->storage = $storage ?: new NativeSessionStorage(); From 3c59f90751b047ba1c6abf0bc278ab4a45fc3760 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 6 Aug 2019 17:03:31 +0200 Subject: [PATCH 11/51] Turned return type annotations of private methods into php return types. --- File/UploadedFile.php | 4 +--- Session/Session.php | 4 +--- Session/Storage/Handler/MongoDbSessionHandler.php | 5 +---- Session/Storage/Handler/PdoSessionHandler.php | 14 ++++---------- Session/Storage/MockFileSessionStorage.php | 4 +--- 5 files changed, 8 insertions(+), 23 deletions(-) diff --git a/File/UploadedFile.php b/File/UploadedFile.php index c33979c8a..91a0525fd 100644 --- a/File/UploadedFile.php +++ b/File/UploadedFile.php @@ -251,10 +251,8 @@ public static function getMaxFilesize() /** * Returns the given size from an ini value in bytes. - * - * @return int The given size in bytes */ - private static function parseFilesize($size) + private static function parseFilesize($size): int { if ('' === $size) { return 0; diff --git a/Session/Session.php b/Session/Session.php index 3e0e0f6da..eb1dc2af9 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -267,10 +267,8 @@ public function getFlashBag() * Gets the attributebag interface. * * Note that this method was added to help with IDE autocompletion. - * - * @return AttributeBagInterface */ - private function getAttributeBag() + private function getAttributeBag(): AttributeBagInterface { return $this->getBag($this->attributeName); } diff --git a/Session/Storage/Handler/MongoDbSessionHandler.php b/Session/Storage/Handler/MongoDbSessionHandler.php index 4efaf412a..536c2840e 100644 --- a/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/Session/Storage/Handler/MongoDbSessionHandler.php @@ -171,10 +171,7 @@ protected function doRead($sessionId) return $dbData[$this->options['data_field']]->getData(); } - /** - * @return \MongoDB\Collection - */ - private function getCollection() + private function getCollection(): \MongoDB\Collection { if (null === $this->collection) { $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index f40d9ec60..5c8eb5f5f 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -434,11 +434,9 @@ private function connect(string $dsn) /** * Builds a PDO DSN from a URL-like connection string. * - * @return string - * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ - private function buildDsnFromUrl(string $dsnOrUrl) + private function buildDsnFromUrl(string $dsnOrUrl): string { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); @@ -672,7 +670,7 @@ protected function doRead($sessionId) * - for oci using DBMS_LOCK.REQUEST * - for sqlsrv using sp_getapplock with LockOwner = Session */ - private function doAdvisoryLock(string $sessionId) + private function doAdvisoryLock(string $sessionId): \PDOStatement { switch ($this->driver) { case 'mysql': @@ -770,10 +768,8 @@ private function getSelectSql(): string /** * Returns an insert statement supported by the database for writing session data. - * - * @return \PDOStatement The insert statement */ - private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime) + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': @@ -799,10 +795,8 @@ private function getInsertStatement(string $sessionId, string $sessionData, int /** * Returns an update statement supported by the database for writing session data. - * - * @return \PDOStatement The update statement */ - private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime) + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php index c0316c2c7..b0996abc5 100644 --- a/Session/Storage/MockFileSessionStorage.php +++ b/Session/Storage/MockFileSessionStorage.php @@ -131,10 +131,8 @@ private function destroy() /** * Calculate path to file. - * - * @return string File path */ - private function getFilePath() + private function getFilePath(): string { return $this->savePath.'/'.$this->id.'.mocksess'; } From 1e64c83e8b5395e7a9beef8833ff99f904d664dd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 7 Aug 2019 17:24:11 +0200 Subject: [PATCH 12/51] Remove unneeded phpdocs --- Request.php | 5 ----- Session/Storage/MockFileSessionStorage.php | 5 ++--- Session/Storage/NativeSessionStorage.php | 1 - Session/Storage/PhpBridgeSessionStorage.php | 1 - 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Request.php b/Request.php index 089793ed3..21f865215 100644 --- a/Request.php +++ b/Request.php @@ -745,11 +745,6 @@ public function hasSession() return null !== $this->session; } - /** - * Sets the Session. - * - * @param SessionInterface $session The Session - */ public function setSession(SessionInterface $session) { $this->session = $session; diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php index b0996abc5..7916fa819 100644 --- a/Session/Storage/MockFileSessionStorage.php +++ b/Session/Storage/MockFileSessionStorage.php @@ -27,9 +27,8 @@ class MockFileSessionStorage extends MockArraySessionStorage private $savePath; /** - * @param string $savePath Path of directory to save session files - * @param string $name Session name - * @param MetadataBag $metaBag MetadataBag instance + * @param string $savePath Path of directory to save session files + * @param string $name Session name */ public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) { diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php index 9e03bfa41..297b114a0 100644 --- a/Session/Storage/NativeSessionStorage.php +++ b/Session/Storage/NativeSessionStorage.php @@ -99,7 +99,6 @@ class NativeSessionStorage implements SessionStorageInterface * * @param array $options Session configuration options * @param \SessionHandlerInterface|null $handler - * @param MetadataBag $metaBag MetadataBag */ public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) { diff --git a/Session/Storage/PhpBridgeSessionStorage.php b/Session/Storage/PhpBridgeSessionStorage.php index 8969e609a..f0fac5843 100644 --- a/Session/Storage/PhpBridgeSessionStorage.php +++ b/Session/Storage/PhpBridgeSessionStorage.php @@ -20,7 +20,6 @@ class PhpBridgeSessionStorage extends NativeSessionStorage { /** * @param \SessionHandlerInterface|null $handler - * @param MetadataBag $metaBag MetadataBag */ public function __construct($handler = null, MetadataBag $metaBag = null) { From 65775d28568174fd4b8bd44c58f0e1cdd0dcd787 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Thu, 8 Aug 2019 16:01:55 +0200 Subject: [PATCH 13/51] cleanup remaining param and internal Intl FulLTransformer --- Session/Storage/Handler/MemcachedSessionHandler.php | 1 - Session/Storage/Handler/MongoDbSessionHandler.php | 3 --- Session/Storage/Handler/PdoSessionHandler.php | 1 - Session/Storage/Handler/RedisSessionHandler.php | 1 - Session/Storage/NativeSessionStorage.php | 1 - 5 files changed, 7 deletions(-) diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php index 1db590b36..bcaeef978 100644 --- a/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/Session/Storage/Handler/MemcachedSessionHandler.php @@ -41,7 +41,6 @@ class MemcachedSessionHandler extends AbstractSessionHandler * * expiretime: The time to live in seconds. * * @param \Memcached $memcached A \Memcached instance - * @param array $options An associative array of Memcached options * * @throws \InvalidArgumentException When unsupported options are passed */ diff --git a/Session/Storage/Handler/MongoDbSessionHandler.php b/Session/Storage/Handler/MongoDbSessionHandler.php index 536c2840e..fd8f05a22 100644 --- a/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/Session/Storage/Handler/MongoDbSessionHandler.php @@ -61,9 +61,6 @@ class MongoDbSessionHandler extends AbstractSessionHandler * If you use such an index, you can drop `gc_probability` to 0 since * no garbage-collection is required. * - * @param \MongoDB\Client $mongo A MongoDB\Client instance - * @param array $options An associative array of field options - * * @throws \InvalidArgumentException When "database" or "collection" not provided */ public function __construct(\MongoDB\Client $mongo, array $options) diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index 5c8eb5f5f..4e044c69b 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -165,7 +165,6 @@ class PdoSessionHandler extends AbstractSessionHandler * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] * * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null - * @param array $options An associative array of options * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php index a6498b882..c2e7d34dc 100644 --- a/Session/Storage/Handler/RedisSessionHandler.php +++ b/Session/Storage/Handler/RedisSessionHandler.php @@ -35,7 +35,6 @@ class RedisSessionHandler extends AbstractSessionHandler * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server. * * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|RedisProxy $redis - * @param array $options An associative array of options * * @throws \InvalidArgumentException When unsupported client or options are passed */ diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php index 297b114a0..98b3199e9 100644 --- a/Session/Storage/NativeSessionStorage.php +++ b/Session/Storage/NativeSessionStorage.php @@ -97,7 +97,6 @@ class NativeSessionStorage implements SessionStorageInterface * trans_sid_hosts, $_SERVER['HTTP_HOST'] * trans_sid_tags, "a=href,area=href,frame=src,form=" * - * @param array $options Session configuration options * @param \SessionHandlerInterface|null $handler */ public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) From 8cfd6583ab0685847807c6883cc660f2c943b8c0 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 20 Jun 2019 21:59:08 +0200 Subject: [PATCH 14/51] [HttpFoundation] deprecate using $first in get and added key in all --- CHANGELOG.md | 1 + HeaderBag.php | 35 +++++++++++++------------- Response.php | 2 +- ResponseHeaderBag.php | 11 +++++++- Test/Constraint/ResponseHeaderSame.php | 2 +- Tests/HeaderBagTest.php | 18 ++++++++++--- Tests/ResponseHeaderBagTest.php | 6 ++--- 7 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e06e678..4828df888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * passing arguments to `Request::isMethodSafe()` is deprecated. * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead 4.3.0 ----- diff --git a/HeaderBag.php b/HeaderBag.php index fa9d17313..ef549f3c3 100644 --- a/HeaderBag.php +++ b/HeaderBag.php @@ -58,10 +58,18 @@ public function __toString() /** * Returns the headers. * + * @param string|null $key The name of the headers to return or null to get them all + * * @return array An array of headers */ - public function all() + public function all(/*string $key = null*/) { + if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) { + $key = str_replace('_', '-', strtolower($key)); + + return $this->headers[$key] ?? []; + } + return $this->headers; } @@ -103,28 +111,21 @@ public function add(array $headers) * * @param string $key The header name * @param string|null $default The default value - * @param bool $first Whether to return the first value or all header values * - * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise + * @return string|null The first header value or default value */ - public function get($key, $default = null, $first = true) + public function get($key, $default = null) { - $key = str_replace('_', '-', strtolower($key)); - $headers = $this->all(); + $headers = $this->all((string) $key); + if (2 < \func_num_args()) { + @trigger_error(sprintf('Passing a third argument to "%s()" is deprecated since Symfony 4.4, use method "all()" instead', __METHOD__), E_USER_DEPRECATED); - if (!\array_key_exists($key, $headers)) { - if (null === $default) { - return $first ? null : []; + if (!func_get_arg(2)) { + return $headers; } - - return $first ? $default : [$default]; - } - - if ($first) { - return \count($headers[$key]) ? $headers[$key][0] : $default; } - return $headers[$key]; + return $headers[0] ?? $default; } /** @@ -181,7 +182,7 @@ public function has($key) */ public function contains($key, $value) { - return \in_array($value, $this->get($key, null, false)); + return \in_array($value, $this->all((string) $key)); } /** diff --git a/Response.php b/Response.php index 168155849..fa5682cbd 100644 --- a/Response.php +++ b/Response.php @@ -1024,7 +1024,7 @@ public function hasVary(): bool */ public function getVary(): array { - if (!$vary = $this->headers->get('Vary', null, false)) { + if (!$vary = $this->headers->all('Vary')) { return []; } diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php index cf44d0ece..0cfc6f6ac 100644 --- a/ResponseHeaderBag.php +++ b/ResponseHeaderBag.php @@ -87,10 +87,19 @@ public function replace(array $headers = []) /** * {@inheritdoc} + * + * @param string|null $key The name of the headers to return or null to get them all */ - public function all() + public function all(/*string $key = null*/) { $headers = parent::all(); + + if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) { + $key = str_replace('_', '-', strtolower($key)); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + foreach ($this->getCookies() as $cookie) { $headers['set-cookie'][] = (string) $cookie; } diff --git a/Test/Constraint/ResponseHeaderSame.php b/Test/Constraint/ResponseHeaderSame.php index acdea71d1..a27d0c73f 100644 --- a/Test/Constraint/ResponseHeaderSame.php +++ b/Test/Constraint/ResponseHeaderSame.php @@ -40,7 +40,7 @@ public function toString(): string */ protected function matches($response): bool { - return $this->expectedValue === $response->headers->get($this->headerName, null, true); + return $this->expectedValue === $response->headers->get($this->headerName, null); } /** diff --git a/Tests/HeaderBagTest.php b/Tests/HeaderBagTest.php index 6c4915f2e..4c8fe8d3e 100644 --- a/Tests/HeaderBagTest.php +++ b/Tests/HeaderBagTest.php @@ -88,16 +88,26 @@ public function testGet() $bag = new HeaderBag(['foo' => 'bar', 'fuzz' => 'bizz']); $this->assertEquals('bar', $bag->get('foo'), '->get return current value'); $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive'); - $this->assertEquals(['bar'], $bag->get('foo', 'nope', false), '->get return the value as array'); + $this->assertEquals(['bar'], $bag->all('foo'), '->get return the value as array'); // defaults $this->assertNull($bag->get('none'), '->get unknown values returns null'); $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default'); - $this->assertEquals(['default'], $bag->get('none', 'default', false), '->get unknown values returns default as array'); + $this->assertEquals([], $bag->all('none'), '->get unknown values returns an empty array'); $bag->set('foo', 'bor', false); $this->assertEquals('bar', $bag->get('foo'), '->get return first value'); - $this->assertEquals(['bar', 'bor'], $bag->get('foo', 'nope', false), '->get return all values as array'); + $this->assertEquals(['bar', 'bor'], $bag->all('foo'), '->get return all values as array'); + } + + /** + * @group legacy + * @expectedDeprecation Passing a third argument to "Symfony\Component\HttpFoundation\HeaderBag::get()" is deprecated since Symfony 4.4, use method "all()" instead + */ + public function testGetIsEqualToNewMethod() + { + $bag = new HeaderBag(['foo' => 'bar', 'fuzz' => 'bizz']); + $this->assertSame($bag->all('none'), $bag->get('none', [], false), '->get unknown values returns default as array'); } public function testSetAssociativeArray() @@ -105,7 +115,7 @@ public function testSetAssociativeArray() $bag = new HeaderBag(); $bag->set('foo', ['bad-assoc-index' => 'value']); $this->assertSame('value', $bag->get('foo')); - $this->assertEquals(['value'], $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored'); + $this->assertSame(['value'], $bag->all('foo'), 'assoc indices of multi-valued headers are ignored'); } public function testContains() diff --git a/Tests/ResponseHeaderBagTest.php b/Tests/ResponseHeaderBagTest.php index 35df36c1c..72cddfcae 100644 --- a/Tests/ResponseHeaderBagTest.php +++ b/Tests/ResponseHeaderBagTest.php @@ -89,13 +89,13 @@ public function testCacheControlHeader() $bag = new ResponseHeaderBag(); $bag->set('Cache-Control', ['public', 'must-revalidate']); - $this->assertCount(1, $bag->get('Cache-Control', null, false)); + $this->assertCount(1, $bag->all('Cache-Control')); $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control')); $bag = new ResponseHeaderBag(); $bag->set('Cache-Control', 'public'); $bag->set('Cache-Control', 'must-revalidate', false); - $this->assertCount(1, $bag->get('Cache-Control', null, false)); + $this->assertCount(1, $bag->all('Cache-Control')); $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control')); } @@ -166,7 +166,7 @@ public function testCookiesWithSameNames() 'foo=bar; path=/path/bar; domain=foo.bar; httponly; samesite=lax', 'foo=bar; path=/path/bar; domain=bar.foo; httponly; samesite=lax', 'foo=bar; path=/; httponly; samesite=lax', - ], $bag->get('set-cookie', null, false)); + ], $bag->all('set-cookie')); $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly; samesite=lax', $bag); $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly; samesite=lax', $bag); From 92e98d936e8e06464413f391375f2af9a05a1336 Mon Sep 17 00:00:00 2001 From: azjezz Date: Sun, 11 Aug 2019 11:15:18 +0200 Subject: [PATCH 15/51] [HttpFoundation] some cleanups --- AcceptHeader.php | 2 +- BinaryFileResponse.php | 2 +- File/Exception/AccessDeniedException.php | 3 --- File/Exception/FileNotFoundException.php | 3 --- File/File.php | 5 ++++- FileBag.php | 2 +- HeaderBag.php | 12 ++---------- ParameterBag.php | 7 ------- Request.php | 12 ++++++------ RequestMatcher.php | 3 --- Response.php | 4 ++-- ResponseHeaderBag.php | 2 +- Session/Attribute/AttributeBagInterface.php | 7 +------ Session/Session.php | 2 +- Session/SessionBagProxy.php | 2 +- Session/SessionInterface.php | 16 +++++++--------- .../Storage/Handler/MemcachedSessionHandler.php | 2 -- Session/Storage/Handler/PdoSessionHandler.php | 8 ++++---- Session/Storage/MetadataBag.php | 2 +- Session/Storage/MockFileSessionStorage.php | 4 ++-- Session/Storage/NativeSessionStorage.php | 4 ++-- Session/Storage/PhpBridgeSessionStorage.php | 4 +++- StreamedResponse.php | 2 -- 23 files changed, 40 insertions(+), 70 deletions(-) diff --git a/AcceptHeader.php b/AcceptHeader.php index 3f5fbb8f3..bbbd62a6d 100644 --- a/AcceptHeader.php +++ b/AcceptHeader.php @@ -153,7 +153,7 @@ public function first() /** * Sorts items by descending quality. */ - private function sort() + private function sort(): void { if (!$this->sorted) { uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php index 79329e2e2..4fd4aec1a 100644 --- a/BinaryFileResponse.php +++ b/BinaryFileResponse.php @@ -269,7 +269,7 @@ public function prepare(Request $request) return $this; } - private function hasValidIfRangeHeader(?string $header) + private function hasValidIfRangeHeader(?string $header): bool { if ($this->getEtag() === $header) { return true; diff --git a/File/Exception/AccessDeniedException.php b/File/Exception/AccessDeniedException.php index c25c3629b..136d2a9f5 100644 --- a/File/Exception/AccessDeniedException.php +++ b/File/Exception/AccessDeniedException.php @@ -18,9 +18,6 @@ */ class AccessDeniedException extends FileException { - /** - * @param string $path The path to the accessed file - */ public function __construct(string $path) { parent::__construct(sprintf('The file %s could not be accessed', $path)); diff --git a/File/Exception/FileNotFoundException.php b/File/Exception/FileNotFoundException.php index 0f1f3f951..31bdf68fe 100644 --- a/File/Exception/FileNotFoundException.php +++ b/File/Exception/FileNotFoundException.php @@ -18,9 +18,6 @@ */ class FileNotFoundException extends FileException { - /** - * @param string $path The path to the file that was not found - */ public function __construct(string $path) { parent::__construct(sprintf('The file "%s" does not exist', $path)); diff --git a/File/File.php b/File/File.php index 396ff3450..4906588a7 100644 --- a/File/File.php +++ b/File/File.php @@ -99,6 +99,9 @@ public function move($directory, $name = null) return $target; } + /** + * @return self + */ protected function getTargetFile($directory, $name = null) { if (!is_dir($directory)) { @@ -119,7 +122,7 @@ protected function getTargetFile($directory, $name = null) * * @param string $name The new file name * - * @return string containing + * @return string */ protected function getName($name) { diff --git a/FileBag.php b/FileBag.php index efd83ffeb..eec3ff446 100644 --- a/FileBag.php +++ b/FileBag.php @@ -24,7 +24,7 @@ class FileBag extends ParameterBag private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type']; /** - * @param array $parameters An array of HTTP files + * @param array|UploadedFile[] $parameters An array of HTTP files */ public function __construct(array $parameters = []) { diff --git a/HeaderBag.php b/HeaderBag.php index ef549f3c3..3d12cb0f6 100644 --- a/HeaderBag.php +++ b/HeaderBag.php @@ -21,9 +21,6 @@ class HeaderBag implements \IteratorAggregate, \Countable protected $headers = []; protected $cacheControl = []; - /** - * @param array $headers An array of HTTP headers - */ public function __construct(array $headers = []) { foreach ($headers as $key => $values) { @@ -85,8 +82,6 @@ public function keys() /** * Replaces the current HTTP headers by a new set. - * - * @param array $headers An array of HTTP headers */ public function replace(array $headers = []) { @@ -96,8 +91,6 @@ public function replace(array $headers = []) /** * Adds new headers the current HTTP headers set. - * - * @param array $headers An array of HTTP headers */ public function add(array $headers) { @@ -204,10 +197,9 @@ public function remove($key) /** * Returns the HTTP header value converted to a date. * - * @param string $key The parameter key - * @param \DateTime $default The default value + * @param string $key The parameter key * - * @return \DateTime|null The parsed DateTime or the default value if the header does not exist + * @return \DateTimeInterface|null The parsed DateTime or the default value if the header does not exist * * @throws \RuntimeException When the HTTP header is not parseable */ diff --git a/ParameterBag.php b/ParameterBag.php index 194ba2c6c..20ca6758b 100644 --- a/ParameterBag.php +++ b/ParameterBag.php @@ -23,9 +23,6 @@ class ParameterBag implements \IteratorAggregate, \Countable */ protected $parameters; - /** - * @param array $parameters An array of parameters - */ public function __construct(array $parameters = []) { $this->parameters = $parameters; @@ -53,8 +50,6 @@ public function keys() /** * Replaces the current parameters by a new set. - * - * @param array $parameters An array of parameters */ public function replace(array $parameters = []) { @@ -63,8 +58,6 @@ public function replace(array $parameters = []) /** * Adds parameters. - * - * @param array $parameters An array of parameters */ public function add(array $parameters = []) { diff --git a/Request.php b/Request.php index 2edc34910..2a980bfa5 100644 --- a/Request.php +++ b/Request.php @@ -632,7 +632,7 @@ public static function getTrustedHosts() */ public static function normalizeQueryString($qs) { - if ('' == $qs) { + if ('' === ($qs ?? '')) { return ''; } @@ -702,7 +702,7 @@ public function get($key, $default = null) /** * Gets the Session. * - * @return SessionInterface|null The session + * @return SessionInterface The session */ public function getSession() { @@ -1591,7 +1591,7 @@ public function getPreferredFormat(?string $default = 'html'): ?string /** * Returns the preferred language. * - * @param array $locales An array of ordered available locales + * @param string[] $locales An array of ordered available locales * * @return string|null The preferred locale */ @@ -1920,7 +1920,7 @@ protected static function initializeFormats() ]; } - private function setPhpDefaultLocale(string $locale) + private function setPhpDefaultLocale(string $locale): void { // if either the class Locale doesn't exist, or an exception is thrown when // setting the default locale, the intl module is not installed, and @@ -1982,7 +1982,7 @@ public function isFromTrustedProxy() return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); } - private function getTrustedValues(int $type, string $ip = null) + private function getTrustedValues(int $type, string $ip = null): array { $clientValues = []; $forwardedValues = []; @@ -2033,7 +2033,7 @@ private function getTrustedValues(int $type, string $ip = null) throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); } - private function normalizeAndFilterClientIps(array $clientIps, string $ip) + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array { if (!$clientIps) { return []; diff --git a/RequestMatcher.php b/RequestMatcher.php index d79c7f2ea..ca90f8b1f 100644 --- a/RequestMatcher.php +++ b/RequestMatcher.php @@ -54,11 +54,8 @@ class RequestMatcher implements RequestMatcherInterface private $schemes = []; /** - * @param string|null $path - * @param string|null $host * @param string|string[]|null $methods * @param string|string[]|null $ips - * @param array $attributes * @param string|string[]|null $schemes */ public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) diff --git a/Response.php b/Response.php index c6a3b32dd..15e1e1bba 100644 --- a/Response.php +++ b/Response.php @@ -1208,7 +1208,7 @@ public function isEmpty(): bool * * @final */ - public static function closeOutputBuffers(int $targetLevel, bool $flush) + public static function closeOutputBuffers(int $targetLevel, bool $flush): void { $status = ob_get_status(true); $level = \count($status); @@ -1230,7 +1230,7 @@ public static function closeOutputBuffers(int $targetLevel, bool $flush) * * @final */ - protected function ensureIEOverSSLCompatibility(Request $request) + protected function ensureIEOverSSLCompatibility(Request $request): void { if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) { if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php index 0cfc6f6ac..79f58eb86 100644 --- a/ResponseHeaderBag.php +++ b/ResponseHeaderBag.php @@ -298,7 +298,7 @@ protected function computeCacheControlValue() return $header; } - private function initDate() + private function initDate(): void { $now = \DateTime::createFromFormat('U', time()); $now->setTimezone(new \DateTimeZone('UTC')); diff --git a/Session/Attribute/AttributeBagInterface.php b/Session/Attribute/AttributeBagInterface.php index 0d8d17991..6fa229397 100644 --- a/Session/Attribute/AttributeBagInterface.php +++ b/Session/Attribute/AttributeBagInterface.php @@ -50,15 +50,10 @@ public function set($name, $value); /** * Returns attributes. * - * @return array Attributes + * @return array */ public function all(); - /** - * Sets attributes. - * - * @param array $attributes Attributes - */ public function replace(array $attributes); /** diff --git a/Session/Session.php b/Session/Session.php index eb1dc2af9..365ef29cf 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -129,7 +129,7 @@ public function getIterator() /** * Returns the number of attributes. * - * @return int The number of attributes + * @return int */ public function count() { diff --git a/Session/SessionBagProxy.php b/Session/SessionBagProxy.php index 3504bdfe7..2ea1cd5a5 100644 --- a/Session/SessionBagProxy.php +++ b/Session/SessionBagProxy.php @@ -63,7 +63,7 @@ public function getName() /** * {@inheritdoc} */ - public function initialize(array &$array) + public function initialize(array &$array): void { ++$this->usageIndex; $this->data[$this->bag->getStorageKey()] = &$array; diff --git a/Session/SessionInterface.php b/Session/SessionInterface.php index 95fca857e..e758c6bda 100644 --- a/Session/SessionInterface.php +++ b/Session/SessionInterface.php @@ -23,7 +23,7 @@ interface SessionInterface /** * Starts the session storage. * - * @return bool True if session started + * @return bool * * @throws \RuntimeException if session fails to start */ @@ -32,7 +32,7 @@ public function start(); /** * Returns the session ID. * - * @return string The session ID + * @return string */ public function getId(); @@ -46,7 +46,7 @@ public function setId($id); /** * Returns the session name. * - * @return mixed The session name + * @return string */ public function getName(); @@ -68,7 +68,7 @@ public function setName($name); * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * - * @return bool True if session invalidated, false if error + * @return bool */ public function invalidate($lifetime = null); @@ -82,7 +82,7 @@ public function invalidate($lifetime = null); * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * - * @return bool True if session migrated, false if error + * @return bool */ public function migrate($destroy = false, $lifetime = null); @@ -100,7 +100,7 @@ public function save(); * * @param string $name The attribute name * - * @return bool true if the attribute is defined, false otherwise + * @return bool */ public function has($name); @@ -125,14 +125,12 @@ public function set($name, $value); /** * Returns attributes. * - * @return array Attributes + * @return array */ public function all(); /** * Sets attributes. - * - * @param array $attributes Attributes */ public function replace(array $attributes); diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php index f1bce4a35..3faa336f6 100644 --- a/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/Session/Storage/Handler/MemcachedSessionHandler.php @@ -40,8 +40,6 @@ class MemcachedSessionHandler extends AbstractSessionHandler * * prefix: The prefix to use for the memcached keys in order to avoid collision * * expiretime: The time to live in seconds. * - * @param \Memcached $memcached A \Memcached instance - * * @throws \InvalidArgumentException When unsupported options are passed */ public function __construct(\Memcached $memcached, array $options = []) diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index 666d7e464..f1648adaa 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -423,7 +423,7 @@ public function close() /** * Lazy-connects to the database. */ - private function connect(string $dsn) + private function connect(string $dsn): void { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -534,7 +534,7 @@ private function buildDsnFromUrl(string $dsnOrUrl): string * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . * So we change it to READ COMMITTED. */ - private function beginTransaction() + private function beginTransaction(): void { if (!$this->inTransaction) { if ('sqlite' === $this->driver) { @@ -552,7 +552,7 @@ private function beginTransaction() /** * Helper method to commit a transaction. */ - private function commit() + private function commit(): void { if ($this->inTransaction) { try { @@ -574,7 +574,7 @@ private function commit() /** * Helper method to rollback a transaction. */ - private function rollback() + private function rollback(): void { // We only need to rollback if we are in a transaction. Otherwise the resulting // error would hide the real problem why rollback was called. We might not be diff --git a/Session/Storage/MetadataBag.php b/Session/Storage/MetadataBag.php index ece3ff34f..5fe40fc10 100644 --- a/Session/Storage/MetadataBag.php +++ b/Session/Storage/MetadataBag.php @@ -159,7 +159,7 @@ public function setName($name) $this->name = $name; } - private function stampCreated(int $lifetime = null) + private function stampCreated(int $lifetime = null): void { $timeStamp = time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php index 7916fa819..02fe4dad4 100644 --- a/Session/Storage/MockFileSessionStorage.php +++ b/Session/Storage/MockFileSessionStorage.php @@ -121,7 +121,7 @@ public function save() * Deletes a session from persistent storage. * Deliberately leaves session data in memory intact. */ - private function destroy() + private function destroy(): void { if (is_file($this->getFilePath())) { unlink($this->getFilePath()); @@ -139,7 +139,7 @@ private function getFilePath(): string /** * Reads session from storage and loads session. */ - private function read() + private function read(): void { $filePath = $this->getFilePath(); $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : []; diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php index 7e177ca50..2a57e6ce8 100644 --- a/Session/Storage/NativeSessionStorage.php +++ b/Session/Storage/NativeSessionStorage.php @@ -97,7 +97,7 @@ class NativeSessionStorage implements SessionStorageInterface * trans_sid_hosts, $_SERVER['HTTP_HOST'] * trans_sid_tags, "a=href,area=href,frame=src,form=" * - * @param \SessionHandlerInterface|null $handler + * @param AbstractProxy|\SessionHandlerInterface|null $handler */ public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) { @@ -409,7 +409,7 @@ public function setOptions(array $options) * @see https://php.net/sessionhandler * @see https://github.com/zikula/NativeSession * - * @param \SessionHandlerInterface|null $saveHandler + * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler * * @throws \InvalidArgumentException */ diff --git a/Session/Storage/PhpBridgeSessionStorage.php b/Session/Storage/PhpBridgeSessionStorage.php index f0fac5843..72dbef134 100644 --- a/Session/Storage/PhpBridgeSessionStorage.php +++ b/Session/Storage/PhpBridgeSessionStorage.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + /** * Allows session to be started by PHP and managed by Symfony. * @@ -19,7 +21,7 @@ class PhpBridgeSessionStorage extends NativeSessionStorage { /** - * @param \SessionHandlerInterface|null $handler + * @param AbstractProxy|\SessionHandlerInterface|null $handler */ public function __construct($handler = null, MetadataBag $metaBag = null) { diff --git a/StreamedResponse.php b/StreamedResponse.php index 8310ea72d..06d7a47ad 100644 --- a/StreamedResponse.php +++ b/StreamedResponse.php @@ -63,8 +63,6 @@ public static function create($callback = null, $status = 200, $headers = []) /** * Sets the PHP callback associated with this Response. * - * @param callable $callback A valid PHP callback - * * @return $this */ public function setCallback(callable $callback) From 4f673837a24963334b905e01585ad34c85a30ccc Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Tue, 13 Aug 2019 12:17:15 -0400 Subject: [PATCH 16/51] [HttpFoundation] Fix deprecation message in ::isMethodSafe() --- Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Request.php b/Request.php index 2a980bfa5..824013ba9 100644 --- a/Request.php +++ b/Request.php @@ -1447,7 +1447,7 @@ public function isMethod($method) public function isMethodSafe() { if (\func_num_args() > 0) { - @trigger_error(sprintf('Passing arguments to "%s()" has been deprecated since Symfony 4.4; use "%s::isMethodCacheable() to check if the method is cacheable instead."', __METHOD__, __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('Passing arguments to "%s()" has been deprecated since Symfony 4.4; use "%s::isMethodCacheable()" to check if the method is cacheable instead.', __METHOD__, __CLASS__), E_USER_DEPRECATED); } return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); From e44dd0c9dc426c64590cca067e2b2025dc8819f7 Mon Sep 17 00:00:00 2001 From: azjezz Date: Wed, 14 Aug 2019 15:53:04 +0100 Subject: [PATCH 17/51] [HttpFoundation] Precalculate session expiry timestamp Co-authored-by: Benjamin Cremer Co-authored-by: Rob Frawley 2nd --- CHANGELOG.md | 5 +- Session/Storage/Handler/PdoSessionHandler.php | 60 ++++++++++++------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62aa6be13..577f27e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ CHANGELOG * passing arguments to `Request::isMethodSafe()` is deprecated. * `ApacheRequest` is deprecated, use the `Request` class instead. * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead - + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + 4.3.0 ----- diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index f1648adaa..043252cd3 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -65,6 +65,8 @@ class PdoSessionHandler extends AbstractSessionHandler */ const LOCK_TRANSACTIONAL = 2; + private const MAX_LIFETIME = 315576000; + /** * @var \PDO|null PDO instance or null when not connected yet */ @@ -237,6 +239,7 @@ public function createTable() try { $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); } catch (\PDOException $e) { $this->rollback(); @@ -368,14 +371,14 @@ protected function doWrite($sessionId, $data) */ public function updateTimestamp($sessionId, $data) { - $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + $expiry = time() + (int) ini_get('session.gc_maxlifetime'); try { $updateStmt = $this->pdo->prepare( - "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" ); $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); - $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); } catch (\PDOException $e) { @@ -402,14 +405,21 @@ public function close() $this->gcCalled = false; // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + // to be removed in 6.0 if ('mysql' === $this->driver) { - $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time"; + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; } else { - $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol"; + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; } - $stmt = $this->pdo->prepare($sql); + $stmt = $this->pdo->prepare($legacySql); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); $stmt->execute(); } @@ -616,7 +626,12 @@ protected function doRead($sessionId) $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); if ($sessionRows) { - if ($sessionRows[0][1] + $sessionRows[0][2] < time()) { + $expiry = (int) $sessionRows[0][1]; + if ($expiry <= self::MAX_LIFETIME) { + $expiry += $sessionRows[0][2]; + } + + if ($expiry < time()) { $this->sessionExpired = true; return ''; @@ -747,6 +762,7 @@ private function getSelectSql(): string if (self::LOCK_TRANSACTIONAL === $this->lockMode) { $this->beginTransaction(); + // selecting the time column should be removed in 6.0 switch ($this->driver) { case 'mysql': case 'oci': @@ -775,18 +791,18 @@ private function getInsertStatement(string $sessionId, string $sessionData, int $data = fopen('php://memory', 'r+'); fwrite($data, $sessionData); rewind($data); - $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data"; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; break; default: $data = $sessionData; - $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); return $stmt; @@ -802,18 +818,18 @@ private function getUpdateStatement(string $sessionId, string $sessionData, int $data = fopen('php://memory', 'r+'); fwrite($data, $sessionData); rewind($data); - $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; break; default: $data = $sessionData; - $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); return $stmt; @@ -826,7 +842,7 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife { switch (true) { case 'mysql' === $this->driver: - $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; break; case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): @@ -837,10 +853,10 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; break; case 'sqlite' === $this->driver: - $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; break; case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): - $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; break; default: @@ -854,15 +870,15 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT); - $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); - $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT); - $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(4, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(5, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(6, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(6, time(), \PDO::PARAM_INT); } else { $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); } From d43988f8b46f937fc1fcacb4930a6060c25bddd9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Aug 2019 13:52:38 +0200 Subject: [PATCH 18/51] Add return types to internal|final|private methods --- Session/Session.php | 4 +--- Session/SessionBagProxy.php | 14 ++++---------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Session/Session.php b/Session/Session.php index 365ef29cf..0c7e9cc5c 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -147,11 +147,9 @@ public function getUsageIndex() } /** - * @return bool - * * @internal */ - public function isEmpty() + public function isEmpty(): bool { if ($this->isStarted()) { ++$this->usageIndex; diff --git a/Session/SessionBagProxy.php b/Session/SessionBagProxy.php index 2ea1cd5a5..09df1426e 100644 --- a/Session/SessionBagProxy.php +++ b/Session/SessionBagProxy.php @@ -29,20 +29,14 @@ public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex $this->usageIndex = &$usageIndex; } - /** - * @return SessionBagInterface - */ - public function getBag() + public function getBag(): SessionBagInterface { ++$this->usageIndex; return $this->bag; } - /** - * @return bool - */ - public function isEmpty() + public function isEmpty(): bool { if (!isset($this->data[$this->bag->getStorageKey()])) { return true; @@ -55,7 +49,7 @@ public function isEmpty() /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return $this->bag->getName(); } @@ -74,7 +68,7 @@ public function initialize(array &$array): void /** * {@inheritdoc} */ - public function getStorageKey() + public function getStorageKey(): string { return $this->bag->getStorageKey(); } From 491b8349828ffde8ecceb871528289cd2033cc26 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Aug 2019 15:30:43 +0200 Subject: [PATCH 19/51] Add return types to tests and final|internal|private methods --- Tests/Session/Storage/NativeSessionStorageTest.php | 5 +---- Tests/Session/Storage/PhpBridgeSessionStorageTest.php | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php index 17f46bef5..b04e06112 100644 --- a/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/Tests/Session/Storage/NativeSessionStorageTest.php @@ -53,10 +53,7 @@ protected function tearDown(): void $this->savePath = null; } - /** - * @return NativeSessionStorage - */ - protected function getStorage(array $options = []) + protected function getStorage(array $options = []): NativeSessionStorage { $storage = new NativeSessionStorage($options); $storage->registerBag(new AttributeBag()); diff --git a/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/Tests/Session/Storage/PhpBridgeSessionStorageTest.php index 7d6827079..206ff4877 100644 --- a/Tests/Session/Storage/PhpBridgeSessionStorageTest.php +++ b/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -49,10 +49,7 @@ protected function tearDown(): void $this->savePath = null; } - /** - * @return PhpBridgeSessionStorage - */ - protected function getStorage() + protected function getStorage(): PhpBridgeSessionStorage { $storage = new PhpBridgeSessionStorage(); $storage->registerBag(new AttributeBag()); From 0004510f52c38ae240a8a417ae544a28a159d95f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Aug 2019 15:01:48 +0200 Subject: [PATCH 20/51] Add return types to internal & magic methods when possible --- .../Storage/Handler/AbstractSessionHandler.php | 10 +++++----- .../Storage/Handler/MemcachedSessionHandler.php | 6 +++--- .../Storage/Handler/MigratingSessionHandler.php | 16 ++++++++-------- .../Storage/Handler/MongoDbSessionHandler.php | 6 +++--- Session/Storage/Handler/NullSessionHandler.php | 8 ++++---- Session/Storage/Handler/PdoSessionHandler.php | 10 +++++----- Session/Storage/Handler/RedisSessionHandler.php | 2 +- Session/Storage/Handler/StrictSessionHandler.php | 10 +++++----- Session/Storage/Proxy/SessionHandlerProxy.php | 16 ++++++++-------- Tests/ResponseTest.php | 2 +- 10 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Session/Storage/Handler/AbstractSessionHandler.php b/Session/Storage/Handler/AbstractSessionHandler.php index 78340efbd..bcde59ee6 100644 --- a/Session/Storage/Handler/AbstractSessionHandler.php +++ b/Session/Storage/Handler/AbstractSessionHandler.php @@ -29,7 +29,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess private $igbinaryEmptyData; /** - * {@inheritdoc} + * @return bool */ public function open($savePath, $sessionName) { @@ -64,7 +64,7 @@ abstract protected function doWrite($sessionId, $data); abstract protected function doDestroy($sessionId); /** - * {@inheritdoc} + * @return bool */ public function validateId($sessionId) { @@ -75,7 +75,7 @@ public function validateId($sessionId) } /** - * {@inheritdoc} + * @return string */ public function read($sessionId) { @@ -98,7 +98,7 @@ public function read($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function write($sessionId, $data) { @@ -115,7 +115,7 @@ public function write($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function destroy($sessionId) { diff --git a/Session/Storage/Handler/MemcachedSessionHandler.php b/Session/Storage/Handler/MemcachedSessionHandler.php index 3faa336f6..7a528e655 100644 --- a/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/Session/Storage/Handler/MemcachedSessionHandler.php @@ -55,7 +55,7 @@ public function __construct(\Memcached $memcached, array $options = []) } /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -71,7 +71,7 @@ protected function doRead($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { @@ -99,7 +99,7 @@ protected function doDestroy($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { diff --git a/Session/Storage/Handler/MigratingSessionHandler.php b/Session/Storage/Handler/MigratingSessionHandler.php index 5293d2448..ccf23de7e 100644 --- a/Session/Storage/Handler/MigratingSessionHandler.php +++ b/Session/Storage/Handler/MigratingSessionHandler.php @@ -39,7 +39,7 @@ public function __construct(\SessionHandlerInterface $currentHandler, \SessionHa } /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -50,7 +50,7 @@ public function close() } /** - * {@inheritdoc} + * @return bool */ public function destroy($sessionId) { @@ -61,7 +61,7 @@ public function destroy($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { @@ -72,7 +72,7 @@ public function gc($maxlifetime) } /** - * {@inheritdoc} + * @return bool */ public function open($savePath, $sessionName) { @@ -83,7 +83,7 @@ public function open($savePath, $sessionName) } /** - * {@inheritdoc} + * @return string */ public function read($sessionId) { @@ -92,7 +92,7 @@ public function read($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function write($sessionId, $sessionData) { @@ -103,7 +103,7 @@ public function write($sessionId, $sessionData) } /** - * {@inheritdoc} + * @return bool */ public function validateId($sessionId) { @@ -112,7 +112,7 @@ public function validateId($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $sessionData) { diff --git a/Session/Storage/Handler/MongoDbSessionHandler.php b/Session/Storage/Handler/MongoDbSessionHandler.php index 53d9369e8..ad1bd24d4 100644 --- a/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/Session/Storage/Handler/MongoDbSessionHandler.php @@ -80,7 +80,7 @@ public function __construct(\MongoDB\Client $mongo, array $options) } /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -100,7 +100,7 @@ protected function doDestroy($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { @@ -134,7 +134,7 @@ protected function doWrite($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { diff --git a/Session/Storage/Handler/NullSessionHandler.php b/Session/Storage/Handler/NullSessionHandler.php index 8d193155b..ba0639604 100644 --- a/Session/Storage/Handler/NullSessionHandler.php +++ b/Session/Storage/Handler/NullSessionHandler.php @@ -19,7 +19,7 @@ class NullSessionHandler extends AbstractSessionHandler { /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -27,7 +27,7 @@ public function close() } /** - * {@inheritdoc} + * @return bool */ public function validateId($sessionId) { @@ -43,7 +43,7 @@ protected function doRead($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { @@ -67,7 +67,7 @@ protected function doDestroy($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index 043252cd3..0cd1296c0 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -260,7 +260,7 @@ public function isSessionExpired() } /** - * {@inheritdoc} + * @return bool */ public function open($savePath, $sessionName) { @@ -274,7 +274,7 @@ public function open($savePath, $sessionName) } /** - * {@inheritdoc} + * @return string */ public function read($sessionId) { @@ -288,7 +288,7 @@ public function read($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { @@ -367,7 +367,7 @@ protected function doWrite($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { @@ -391,7 +391,7 @@ public function updateTimestamp($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function close() { diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php index c2e7d34dc..752e58f73 100644 --- a/Session/Storage/Handler/RedisSessionHandler.php +++ b/Session/Storage/Handler/RedisSessionHandler.php @@ -104,7 +104,7 @@ public function gc($maxlifetime): bool } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { diff --git a/Session/Storage/Handler/StrictSessionHandler.php b/Session/Storage/Handler/StrictSessionHandler.php index 83a1f2c06..31a39a697 100644 --- a/Session/Storage/Handler/StrictSessionHandler.php +++ b/Session/Storage/Handler/StrictSessionHandler.php @@ -31,7 +31,7 @@ public function __construct(\SessionHandlerInterface $handler) } /** - * {@inheritdoc} + * @return bool */ public function open($savePath, $sessionName) { @@ -49,7 +49,7 @@ protected function doRead($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { @@ -65,7 +65,7 @@ protected function doWrite($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function destroy($sessionId) { @@ -86,7 +86,7 @@ protected function doDestroy($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -94,7 +94,7 @@ public function close() } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { diff --git a/Session/Storage/Proxy/SessionHandlerProxy.php b/Session/Storage/Proxy/SessionHandlerProxy.php index b11cc397a..8034b7a46 100644 --- a/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/Session/Storage/Proxy/SessionHandlerProxy.php @@ -36,7 +36,7 @@ public function getHandler() // \SessionHandlerInterface /** - * {@inheritdoc} + * @return bool */ public function open($savePath, $sessionName) { @@ -44,7 +44,7 @@ public function open($savePath, $sessionName) } /** - * {@inheritdoc} + * @return bool */ public function close() { @@ -52,7 +52,7 @@ public function close() } /** - * {@inheritdoc} + * @return string */ public function read($sessionId) { @@ -60,7 +60,7 @@ public function read($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function write($sessionId, $data) { @@ -68,7 +68,7 @@ public function write($sessionId, $data) } /** - * {@inheritdoc} + * @return bool */ public function destroy($sessionId) { @@ -76,7 +76,7 @@ public function destroy($sessionId) } /** - * {@inheritdoc} + * @return int */ public function gc($maxlifetime) { @@ -84,7 +84,7 @@ public function gc($maxlifetime) } /** - * {@inheritdoc} + * @return bool */ public function validateId($sessionId) { @@ -92,7 +92,7 @@ public function validateId($sessionId) } /** - * {@inheritdoc} + * @return bool */ public function updateTimestamp($sessionId, $data) { diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php index 03b29653e..edce7dbf4 100644 --- a/Tests/ResponseTest.php +++ b/Tests/ResponseTest.php @@ -1054,7 +1054,7 @@ public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase) class StringableObject { - public function __toString() + public function __toString(): string { return 'Foo'; } From 39869c96a715e509d1f1a2d4980d1971adecc2ab Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 23 Aug 2019 14:04:07 +0200 Subject: [PATCH 21/51] Add more return types after fixing a typo in my script --- Session/Session.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Session/Session.php b/Session/Session.php index 0c7e9cc5c..9738189b4 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -137,11 +137,9 @@ public function count() } /** - * @return int - * * @internal */ - public function getUsageIndex() + public function getUsageIndex(): int { return $this->usageIndex; } From 5b4bb0d2930d8826adc3e638e273735926a030c3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 23 Aug 2019 15:32:15 +0200 Subject: [PATCH 22/51] Add return-types with help from DebugClassLoader in the CI --- Session/Storage/Handler/MongoDbSessionHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Storage/Handler/MongoDbSessionHandler.php b/Session/Storage/Handler/MongoDbSessionHandler.php index ad1bd24d4..27e080021 100644 --- a/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/Session/Storage/Handler/MongoDbSessionHandler.php @@ -100,7 +100,7 @@ protected function doDestroy($sessionId) } /** - * @return int + * @return bool */ public function gc($maxlifetime) { From 4bd8415d6c3e8f1e2656e76fbad0b9fdddc82b25 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 26 Aug 2019 17:52:30 +0200 Subject: [PATCH 23/51] [ErrorHandler] make DebugClassLoader turn multi-types declarations to "object" --- .../Session/Storage/Handler/RedisArraySessionHandlerTest.php | 5 ++++- .../Storage/Handler/RedisClusterSessionHandlerTest.php | 5 ++++- Tests/Session/Storage/Handler/RedisSessionHandlerTest.php | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php index b03a37236..3ef6cb694 100644 --- a/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php @@ -13,7 +13,10 @@ class RedisArraySessionHandlerTest extends AbstractRedisSessionHandlerTestCase { - protected function createRedisClient(string $host): \RedisArray + /** + * @return \RedisArray|object + */ + protected function createRedisClient(string $host) { return new \RedisArray([$host]); } diff --git a/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php index c1ba70dcb..8b4cd1cdd 100644 --- a/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php @@ -24,7 +24,10 @@ public static function setUpBeforeClass(): void } } - protected function createRedisClient(string $host): \RedisCluster + /** + * @return \RedisCluster|object + */ + protected function createRedisClient(string $host) { return new \RedisCluster(null, explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); } diff --git a/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php index afdb6c503..71658f072 100644 --- a/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php @@ -13,7 +13,10 @@ class RedisSessionHandlerTest extends AbstractRedisSessionHandlerTestCase { - protected function createRedisClient(string $host): \Redis + /** + * @return \Redis|object + */ + protected function createRedisClient(string $host) { $client = new \Redis(); $client->connect($host); From 089da40dfa1e5f0a89f29619a1337a64d88e08f9 Mon Sep 17 00:00:00 2001 From: Daniel Rotter Date: Tue, 27 Aug 2019 10:31:03 +0200 Subject: [PATCH 24/51] Return null as Expire header if it was set to null --- HeaderBag.php | 10 +++++++++- Tests/HeaderBagTest.php | 10 ++++++++++ Tests/ResponseTest.php | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/HeaderBag.php b/HeaderBag.php index 08299977c..35bd6ad8f 100644 --- a/HeaderBag.php +++ b/HeaderBag.php @@ -121,7 +121,15 @@ public function get($key, $default = null, $first = true) } if ($first) { - return \count($headers[$key]) ? (string) $headers[$key][0] : $default; + if (!$headers[$key]) { + return $default; + } + + if (null === $headers[$key][0]) { + return null; + } + + return (string) $headers[$key][0]; } return $headers[$key]; diff --git a/Tests/HeaderBagTest.php b/Tests/HeaderBagTest.php index a5876f9e3..dcc266f69 100644 --- a/Tests/HeaderBagTest.php +++ b/Tests/HeaderBagTest.php @@ -48,6 +48,13 @@ public function testGetDate() $this->assertInstanceOf('DateTime', $headerDate); } + public function testGetDateNull() + { + $bag = new HeaderBag(['foo' => null]); + $headerDate = $bag->getDate('foo'); + $this->assertNull($headerDate); + } + public function testGetDateException() { $this->expectException('RuntimeException'); @@ -96,6 +103,9 @@ public function testGet() $bag->set('foo', 'bor', false); $this->assertEquals('bar', $bag->get('foo'), '->get return first value'); $this->assertEquals(['bar', 'bor'], $bag->get('foo', 'nope', false), '->get return all values as array'); + + $bag->set('baz', null); + $this->assertNull($bag->get('baz', 'nope'), '->get return null although different default value is given'); } public function testSetAssociativeArray() diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php index c61dbacdd..b846cdad3 100644 --- a/Tests/ResponseTest.php +++ b/Tests/ResponseTest.php @@ -369,6 +369,12 @@ public function testExpire() $this->assertNull($response->headers->get('Expires'), '->expire() removes the Expires header when the response is fresh'); } + public function testNullExpireHeader() + { + $response = new Response(null, 200, ['Expires' => null]); + $this->assertNull($response->getExpires()); + } + public function testGetTtl() { $response = new Response(); From df7421ae2eb01af58afc98087e3984cef4f323ec Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 1 Sep 2019 18:05:42 +0200 Subject: [PATCH 25/51] [HttpFoundation] Add types to private/final/internal methods and constructors. --- Request.php | 16 +++++++--------- Session/SessionBagProxy.php | 2 +- Test/Constraint/RequestAttributeValueSame.php | 1 + Test/Constraint/ResponseHasCookie.php | 2 +- Tests/RequestTest.php | 4 ++-- Tests/ResponseHeaderBagTest.php | 2 +- .../Handler/MongoDbSessionHandlerTest.php | 2 +- .../Storage/Handler/PdoSessionHandlerTest.php | 5 ++++- .../Storage/MockFileSessionStorageTest.php | 2 +- 9 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Request.php b/Request.php index 866fea4fb..4510f7c52 100644 --- a/Request.php +++ b/Request.php @@ -1817,12 +1817,12 @@ protected function prepareBaseUrl() $requestUri = '/'.$requestUri; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { // full $baseUrl matches return $prefix; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { // directory portion of $baseUrl matches return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); } @@ -1941,14 +1941,12 @@ private function setPhpDefaultLocale(string $locale): void /** * Returns the prefix as encoded in the string when the string starts with - * the given prefix, false otherwise. - * - * @return string|false The prefix as it is encoded in $string, or false + * the given prefix, null otherwise. */ - private function getUrlencodedPrefix(string $string, string $prefix) + private function getUrlencodedPrefix(string $string, string $prefix): ?string { if (0 !== strpos(rawurldecode($string), $prefix)) { - return false; + return null; } $len = \strlen($prefix); @@ -1957,10 +1955,10 @@ private function getUrlencodedPrefix(string $string, string $prefix) return $match[0]; } - return false; + return null; } - private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self { if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); diff --git a/Session/SessionBagProxy.php b/Session/SessionBagProxy.php index 09df1426e..0ae8231ef 100644 --- a/Session/SessionBagProxy.php +++ b/Session/SessionBagProxy.php @@ -22,7 +22,7 @@ final class SessionBagProxy implements SessionBagInterface private $data; private $usageIndex; - public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex) + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex) { $this->bag = $bag; $this->data = &$data; diff --git a/Test/Constraint/RequestAttributeValueSame.php b/Test/Constraint/RequestAttributeValueSame.php index 2d1056278..cb216ea12 100644 --- a/Test/Constraint/RequestAttributeValueSame.php +++ b/Test/Constraint/RequestAttributeValueSame.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Test\Constraint; use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; final class RequestAttributeValueSame extends Constraint { diff --git a/Test/Constraint/ResponseHasCookie.php b/Test/Constraint/ResponseHasCookie.php index bd792b0d8..eae9e271b 100644 --- a/Test/Constraint/ResponseHasCookie.php +++ b/Test/Constraint/ResponseHasCookie.php @@ -64,7 +64,7 @@ protected function failureDescription($response): string return 'the Response '.$this->toString(); } - protected function getCookie(Response $response): ?Cookie + private function getCookie(Response $response): ?Cookie { $cookies = $response->headers->getCookies(); diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index f13582deb..8febdf629 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -1803,7 +1803,7 @@ private function disableHttpMethodParameterOverride() $property->setValue(false); } - private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies) + private function getRequestInstanceForClientIpTests(string $remoteAddr, ?string $httpForwardedFor, ?array $trustedProxies): Request { $request = new Request(); @@ -1821,7 +1821,7 @@ private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedF return $request; } - private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies) + private function getRequestInstanceForClientIpsForwardedTests(string $remoteAddr, ?string $httpForwarded, ?array $trustedProxies): Request { $request = new Request(); diff --git a/Tests/ResponseHeaderBagTest.php b/Tests/ResponseHeaderBagTest.php index d9e73499a..1a3817333 100644 --- a/Tests/ResponseHeaderBagTest.php +++ b/Tests/ResponseHeaderBagTest.php @@ -299,7 +299,7 @@ public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced() $this->assertTrue($bag->has('Date')); } - private function assertSetCookieHeader($expected, ResponseHeaderBag $actual) + private function assertSetCookieHeader(string $expected, ResponseHeaderBag $actual) { $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual)); } diff --git a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index f956fa276..0adabc02c 100644 --- a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -197,7 +197,7 @@ public function testGetConnection() $this->assertInstanceOf(\MongoDB\Client::class, $method->invoke($this->storage)); } - private function createMongoCollectionMock() + private function createMongoCollectionMock(): \MongoDB\Collection { $collection = $this->getMockBuilder(\MongoDB\Collection::class) ->disableOriginalConstructor() diff --git a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index d080ce3ca..b8c097763 100644 --- a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -346,6 +346,9 @@ public function provideUrlDsnPairs() yield ['mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test']; } + /** + * @return resource + */ private function createStream($content) { $stream = tmpfile(); @@ -362,7 +365,7 @@ class MockPdo extends \PDO private $driverName; private $errorMode; - public function __construct($driverName = null, $errorMode = null) + public function __construct(string $driverName = null, int $errorMode = null) { $this->driverName = $driverName; $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION; diff --git a/Tests/Session/Storage/MockFileSessionStorageTest.php b/Tests/Session/Storage/MockFileSessionStorageTest.php index d9314075a..2231bda85 100644 --- a/Tests/Session/Storage/MockFileSessionStorageTest.php +++ b/Tests/Session/Storage/MockFileSessionStorageTest.php @@ -114,7 +114,7 @@ public function testSaveWithoutStart() $storage1->save(); } - private function getStorage() + private function getStorage(): MockFileSessionStorage { $storage = new MockFileSessionStorage($this->sessionDir); $storage->registerBag(new FlashBag()); From b90f3d5f9c3397e8b693d7c8acdeff8de0aef4cc Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 5 Sep 2019 17:26:55 +0200 Subject: [PATCH 26/51] Adding .gitattributes to remove Tests directory from "dist" --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..aa02dc651 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore From ec2a74a7c858640acaebd11a9ebc2a8f6662f7aa Mon Sep 17 00:00:00 2001 From: mmokhi Date: Wed, 11 Sep 2019 11:18:08 +0200 Subject: [PATCH 27/51] Call AssertEquals with proper parameters Since `$response->getContent()` returns string and our first parameter is already string as well, in some cases (with different precisions) it may "compare strings" as "strings" and this is not what the test wants. By changing the first parameter to actual number we force `AssertEquals` to compare them numerically rather than literally by string content. --- Tests/JsonResponseTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/JsonResponseTest.php b/Tests/JsonResponseTest.php index fd045fb9c..9642dc28d 100644 --- a/Tests/JsonResponseTest.php +++ b/Tests/JsonResponseTest.php @@ -52,7 +52,7 @@ public function testConstructorWithSimpleTypes() $this->assertSame('0', $response->getContent()); $response = new JsonResponse(0.1); - $this->assertEquals('0.1', $response->getContent()); + $this->assertEquals(0.1, $response->getContent()); $this->assertIsString($response->getContent()); $response = new JsonResponse(true); @@ -141,7 +141,7 @@ public function testStaticCreateWithSimpleTypes() $response = JsonResponse::create(0.1); $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); - $this->assertEquals('0.1', $response->getContent()); + $this->assertEquals(0.1, $response->getContent()); $this->assertIsString($response->getContent()); $response = JsonResponse::create(true); From 876ee3475faa4a3ad38686e2d92ddfa922ed64ba Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Fri, 13 Sep 2019 17:45:08 +0300 Subject: [PATCH 28/51] Replace REMOTE_ADDR in trusted proxies with the current REMOTE_ADDR --- Request.php | 12 ++++++++++-- Tests/RequestTest.php | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Request.php b/Request.php index 4510f7c52..4fae46cb6 100644 --- a/Request.php +++ b/Request.php @@ -567,14 +567,22 @@ public function overrideGlobals() * * You should only list the reverse proxies that you manage directly. * - * @param array $proxies A list of trusted proxies + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies * * @throws \InvalidArgumentException When $trustedHeaderSet is invalid */ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { - self::$trustedProxies = $proxies; + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } + + return $proxies; + }, []); self::$trustedHeaderSet = $trustedHeaderSet; } diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index 8febdf629..1d0164725 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -2324,6 +2324,26 @@ public function testTrustedPortDoesNotDefaultToZero() $this->assertSame(80, $request->getPort()); } + + /** + * @dataProvider trustedProxiesRemoteAddr + */ + public function testTrustedProxiesRemoteAddr($serverRemoteAddr, $trustedProxies, $result) + { + $_SERVER['REMOTE_ADDR'] = $serverRemoteAddr; + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + $this->assertSame($result, Request::getTrustedProxies()); + } + + public function trustedProxiesRemoteAddr() + { + return [ + ['1.1.1.1', ['REMOTE_ADDR'], ['1.1.1.1']], + ['1.1.1.1', ['REMOTE_ADDR', '2.2.2.2'], ['1.1.1.1', '2.2.2.2']], + [null, ['REMOTE_ADDR'], []], + [null, ['REMOTE_ADDR', '2.2.2.2'], ['2.2.2.2']], + ]; + } } class RequestContentProxy extends Request From 4b0a75e9155bb5fe5cfcc52d9e90fc1920b8cae2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 30 Jul 2018 18:06:05 +0200 Subject: [PATCH 29/51] [Security] Make stateful firewalls turn responses private only when needed --- Session/Session.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Session/Session.php b/Session/Session.php index 9738189b4..2192c629e 100644 --- a/Session/Session.php +++ b/Session/Session.php @@ -136,10 +136,7 @@ public function count() return \count($this->getAttributeBag()->all()); } - /** - * @internal - */ - public function getUsageIndex(): int + public function &getUsageIndex(): int { return $this->usageIndex; } From b93f93694cf554eccdb1daaa40796485933ebf71 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 26 Sep 2019 19:03:18 +0200 Subject: [PATCH 30/51] [HttpFoundation] optimize normalization of headers --- HeaderBag.php | 13 +++++++------ Request.php | 2 +- ResponseHeaderBag.php | 8 ++++---- ServerBag.php | 5 +---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/HeaderBag.php b/HeaderBag.php index 5cfa5313a..9ffe6f4fe 100644 --- a/HeaderBag.php +++ b/HeaderBag.php @@ -18,6 +18,9 @@ */ class HeaderBag implements \IteratorAggregate, \Countable { + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + protected $headers = []; protected $cacheControl = []; @@ -62,9 +65,7 @@ public function __toString() public function all(/*string $key = null*/) { if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) { - $key = str_replace('_', '-', strtolower($key)); - - return $this->headers[$key] ?? []; + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; } return $this->headers; @@ -138,7 +139,7 @@ public function get($key, $default = null) */ public function set($key, $values, $replace = true) { - $key = str_replace('_', '-', strtolower($key)); + $key = strtr($key, self::UPPER, self::LOWER); if (\is_array($values)) { $values = array_values($values); @@ -170,7 +171,7 @@ public function set($key, $values, $replace = true) */ public function has($key) { - return \array_key_exists(str_replace('_', '-', strtolower($key)), $this->all()); + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); } /** @@ -193,7 +194,7 @@ public function contains($key, $value) */ public function remove($key) { - $key = str_replace('_', '-', strtolower($key)); + $key = strtr($key, self::UPPER, self::LOWER); unset($this->headers[$key]); diff --git a/Request.php b/Request.php index 4510f7c52..fe0f6de23 100644 --- a/Request.php +++ b/Request.php @@ -541,7 +541,7 @@ public function overrideGlobals() foreach ($this->headers->all() as $key => $value) { $key = strtoupper(str_replace('-', '_', $key)); - if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) { + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { $_SERVER[$key] = implode(', ', $value); } else { $_SERVER['HTTP_'.$key] = implode(', ', $value); diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php index 79f58eb86..f5b7a27fd 100644 --- a/ResponseHeaderBag.php +++ b/ResponseHeaderBag.php @@ -51,7 +51,7 @@ public function allPreserveCase() { $headers = []; foreach ($this->all() as $name => $value) { - $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; + $headers[$this->headerNames[$name] ?? $name] = $value; } return $headers; @@ -95,7 +95,7 @@ public function all(/*string $key = null*/) $headers = parent::all(); if (1 <= \func_num_args() && null !== $key = func_get_arg(0)) { - $key = str_replace('_', '-', strtolower($key)); + $key = strtr($key, self::UPPER, self::LOWER); return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); } @@ -112,7 +112,7 @@ public function all(/*string $key = null*/) */ public function set($key, $values, $replace = true) { - $uniqueKey = str_replace('_', '-', strtolower($key)); + $uniqueKey = strtr($key, self::UPPER, self::LOWER); if ('set-cookie' === $uniqueKey) { if ($replace) { @@ -143,7 +143,7 @@ public function set($key, $values, $replace = true) */ public function remove($key) { - $uniqueKey = str_replace('_', '-', strtolower($key)); + $uniqueKey = strtr($key, self::UPPER, self::LOWER); unset($this->headerNames[$uniqueKey]); if ('set-cookie' === $uniqueKey) { diff --git a/ServerBag.php b/ServerBag.php index 4c82b1774..25da35ec5 100644 --- a/ServerBag.php +++ b/ServerBag.php @@ -28,13 +28,10 @@ class ServerBag extends ParameterBag public function getHeaders() { $headers = []; - $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true]; foreach ($this->parameters as $key => $value) { if (0 === strpos($key, 'HTTP_')) { $headers[substr($key, 5)] = $value; - } - // CONTENT_* are not prefixed with HTTP_ - elseif (isset($contentHeaders[$key])) { + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { $headers[$key] = $value; } } From 35ffbbfa73e46e28a9433692cd6c404de70e6c4c Mon Sep 17 00:00:00 2001 From: marie <15118505+marie@users.noreply.github.com> Date: Fri, 20 Sep 2019 15:03:12 +0500 Subject: [PATCH 31/51] [HttpFoundation] allow additinal characters in not raw cookies --- Cookie.php | 21 +++++++++++++---- Response.php | 2 +- Tests/CookieTest.php | 23 +++++++++++++++---- .../cookie_urlencode.expected | 3 ++- .../response-functional/cookie_urlencode.php | 9 +++++--- .../invalid_cookie_name.php | 2 +- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Cookie.php b/Cookie.php index 83a97087f..98a5ef00a 100644 --- a/Cookie.php +++ b/Cookie.php @@ -18,6 +18,10 @@ */ class Cookie { + const SAMESITE_NONE = 'none'; + const SAMESITE_LAX = 'lax'; + const SAMESITE_STRICT = 'strict'; + protected $name; protected $value; protected $domain; @@ -25,12 +29,13 @@ class Cookie protected $path; protected $secure; protected $httpOnly; + private $raw; private $sameSite; - const SAMESITE_NONE = 'none'; - const SAMESITE_LAX = 'lax'; - const SAMESITE_STRICT = 'strict'; + private static $reservedCharsList = "=,; \t\r\n\v\f"; + private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; /** * Creates cookie from raw header string. @@ -97,7 +102,7 @@ public static function fromString($cookie, $decode = false) public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) { // from PHP source code - if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + if ($raw && false !== strpbrk($name, self::$reservedCharsList)) { throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); } @@ -143,7 +148,13 @@ public function __construct($name, $value = null, $expire = 0, $path = '/', $dom */ public function __toString() { - $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; + if ($this->isRaw()) { + $str = $this->getName(); + } else { + $str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName()); + } + + $str .= '='; if ('' === (string) $this->getValue()) { $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; diff --git a/Response.php b/Response.php index 8a17acdd5..26e3a3378 100644 --- a/Response.php +++ b/Response.php @@ -342,7 +342,7 @@ public function sendHeaders() // cookies foreach ($this->headers->getCookies() as $cookie) { - header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode); + header('Set-Cookie: '.$cookie, false, $this->statusCode); } // status diff --git a/Tests/CookieTest.php b/Tests/CookieTest.php index 38c195df3..169f91787 100644 --- a/Tests/CookieTest.php +++ b/Tests/CookieTest.php @@ -24,10 +24,9 @@ */ class CookieTest extends TestCase { - public function invalidNames() + public function namesWithSpecialCharacters() { return [ - [''], [',MyName'], [';MyName'], [' MyName'], @@ -40,12 +39,26 @@ public function invalidNames() } /** - * @dataProvider invalidNames + * @dataProvider namesWithSpecialCharacters */ - public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + public function testInstantiationThrowsExceptionIfRawCookieNameContainsSpecialCharacters($name) { $this->expectException('InvalidArgumentException'); - new Cookie($name); + new Cookie($name, null, 0, null, null, null, false, true); + } + + /** + * @dataProvider namesWithSpecialCharacters + */ + public function testInstantiationSucceedNonRawCookieNameContainsSpecialCharacters($name) + { + $this->assertInstanceOf(Cookie::class, new Cookie($name)); + } + + public function testInstantiationThrowsExceptionIfCookieNameIsEmpty() + { + $this->expectException('InvalidArgumentException'); + new Cookie(''); } public function testInvalidExpiration() diff --git a/Tests/Fixtures/response-functional/cookie_urlencode.expected b/Tests/Fixtures/response-functional/cookie_urlencode.expected index 14e44a398..17a9efc66 100644 --- a/Tests/Fixtures/response-functional/cookie_urlencode.expected +++ b/Tests/Fixtures/response-functional/cookie_urlencode.expected @@ -4,7 +4,8 @@ Array [0] => Content-Type: text/plain; charset=utf-8 [1] => Cache-Control: no-cache, private [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT - [3] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ + [3] => Set-Cookie: %3D%2C%3B%20%09%0D%0A%0B%0C=%3D%2C%3B%20%09%0D%0A%0B%0C; path=/ [4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ + [5] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ ) shutdown diff --git a/Tests/Fixtures/response-functional/cookie_urlencode.php b/Tests/Fixtures/response-functional/cookie_urlencode.php index 05b9af30d..9ffb0dfec 100644 --- a/Tests/Fixtures/response-functional/cookie_urlencode.php +++ b/Tests/Fixtures/response-functional/cookie_urlencode.php @@ -4,9 +4,12 @@ $r = require __DIR__.'/common.inc'; -$str = '?*():@&+$/%#[]'; +$str1 = "=,; \t\r\n\v\f"; +$r->headers->setCookie(new Cookie($str1, $str1, 0, '', null, false, false, false, null)); -$r->headers->setCookie(new Cookie($str, $str, 0, '', null, false, false)); +$str2 = '?*():@&+$/%#[]'; + +$r->headers->setCookie(new Cookie($str2, $str2, 0, '', null, false, false, false, null)); $r->sendHeaders(); -setcookie($str, $str, 0, '/'); +setcookie($str2, $str2, 0, '/'); diff --git a/Tests/Fixtures/response-functional/invalid_cookie_name.php b/Tests/Fixtures/response-functional/invalid_cookie_name.php index 3fe157184..3acf86039 100644 --- a/Tests/Fixtures/response-functional/invalid_cookie_name.php +++ b/Tests/Fixtures/response-functional/invalid_cookie_name.php @@ -5,7 +5,7 @@ $r = require __DIR__.'/common.inc'; try { - $r->headers->setCookie(new Cookie('Hello + world', 'hodor')); + $r->headers->setCookie(new Cookie('Hello + world', 'hodor', 0, null, null, null, false, true)); } catch (\InvalidArgumentException $e) { echo $e->getMessage(); } From 233f40cbebd595ffd91ddf291355f8a930a13777 Mon Sep 17 00:00:00 2001 From: bogdan Date: Wed, 2 Oct 2019 18:28:34 +0300 Subject: [PATCH 32/51] [HttpFoundation] Check if data passed to SessionBagProxy::initialize is an array --- Session/Storage/NativeSessionStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php index 4f1c30ef5..4c5873728 100644 --- a/Session/Storage/NativeSessionStorage.php +++ b/Session/Storage/NativeSessionStorage.php @@ -430,7 +430,7 @@ protected function loadSession(array &$session = null) foreach ($bags as $bag) { $key = $bag->getStorageKey(); - $session[$key] = isset($session[$key]) ? $session[$key] : []; + $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; $bag->initialize($session[$key]); } From 3daf488b5833a9bd7cfb4b34bcdaba1b0f0b398d Mon Sep 17 00:00:00 2001 From: Reedy Date: Sat, 12 Oct 2019 01:27:05 +0100 Subject: [PATCH 33/51] Add .gitignore to .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index aa02dc651..ebb928704 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore +/.gitignore export-ignore From 4db558c7c6777aac02293efbfe7c7c5d4c1385c3 Mon Sep 17 00:00:00 2001 From: Ilia Lazarev Date: Sat, 12 Oct 2019 10:36:35 +0300 Subject: [PATCH 34/51] Add plus character `+` to legal mime subtype For example, the following mime type (used for epub) is not recognized given the current regexp: `application/epub+zip` --- File/MimeType/FileBinaryMimeTypeGuesser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/File/MimeType/FileBinaryMimeTypeGuesser.php b/File/MimeType/FileBinaryMimeTypeGuesser.php index b75242afd..cfa76843c 100644 --- a/File/MimeType/FileBinaryMimeTypeGuesser.php +++ b/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -89,7 +89,7 @@ public function guess($path) $type = trim(ob_get_clean()); - if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) { // it's not a type, but an error message return null; } From 7b4626ab40d8562707e6d1c9a6fab977a86c2037 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 24 Oct 2019 14:44:17 +0200 Subject: [PATCH 35/51] Remove unused local variables in tests --- Tests/File/UploadedFileTest.php | 2 +- Tests/HeaderBagTest.php | 2 +- Tests/RedirectResponseTest.php | 4 ++-- .../Storage/Handler/NativeFileSessionHandlerTest.php | 6 +++--- Tests/Session/Storage/Handler/NullSessionHandlerTest.php | 2 +- Tests/Session/Storage/Handler/PdoSessionHandlerTest.php | 2 +- Tests/Session/Storage/NativeSessionStorageTest.php | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Tests/File/UploadedFileTest.php b/Tests/File/UploadedFileTest.php index 4186c72a6..2ea924bac 100644 --- a/Tests/File/UploadedFileTest.php +++ b/Tests/File/UploadedFileTest.php @@ -153,7 +153,7 @@ public function testMoveLocalFileIsNotAllowed() UPLOAD_ERR_OK ); - $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + $file->move(__DIR__.'/Fixtures/directory'); } public function testMoveLocalFileIsAllowedInTestMode() diff --git a/Tests/HeaderBagTest.php b/Tests/HeaderBagTest.php index dcc266f69..cabe038bd 100644 --- a/Tests/HeaderBagTest.php +++ b/Tests/HeaderBagTest.php @@ -59,7 +59,7 @@ public function testGetDateException() { $this->expectException('RuntimeException'); $bag = new HeaderBag(['foo' => 'Tue']); - $headerDate = $bag->getDate('foo'); + $bag->getDate('foo'); } public function testGetCacheControlHeader() diff --git a/Tests/RedirectResponseTest.php b/Tests/RedirectResponseTest.php index 92f4876da..e1ff3bf2b 100644 --- a/Tests/RedirectResponseTest.php +++ b/Tests/RedirectResponseTest.php @@ -29,13 +29,13 @@ public function testGenerateMetaRedirect() public function testRedirectResponseConstructorNullUrl() { $this->expectException('InvalidArgumentException'); - $response = new RedirectResponse(null); + new RedirectResponse(null); } public function testRedirectResponseConstructorWrongStatusCode() { $this->expectException('InvalidArgumentException'); - $response = new RedirectResponse('foo.bar', 404); + new RedirectResponse('foo.bar', 404); } public function testGenerateLocationHeader() diff --git a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php index 9daf1a0ee..7de55798a 100644 --- a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -41,7 +41,7 @@ public function testConstruct() */ public function testConstructSavePath($savePath, $expectedSavePath, $path) { - $handler = new NativeFileSessionHandler($savePath); + new NativeFileSessionHandler($savePath); $this->assertEquals($expectedSavePath, ini_get('session.save_path')); $this->assertDirectoryExists(realpath($path)); @@ -62,13 +62,13 @@ public function savePathDataProvider() public function testConstructException() { $this->expectException('InvalidArgumentException'); - $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args'); + new NativeFileSessionHandler('something;invalid;with;too-many-args'); } public function testConstructDefault() { $path = ini_get('session.save_path'); - $storage = new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler()); + new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler()); $this->assertEquals($path, ini_get('session.save_path')); } diff --git a/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/Tests/Session/Storage/Handler/NullSessionHandlerTest.php index 0d246e1aa..f793db144 100644 --- a/Tests/Session/Storage/Handler/NullSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -28,7 +28,7 @@ class NullSessionHandlerTest extends TestCase { public function testSaveHandlers() { - $storage = $this->getStorage(); + $this->getStorage(); $this->assertEquals('user', ini_get('session.save_handler')); } diff --git a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index 123b605d2..e710dca92 100644 --- a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -54,7 +54,7 @@ public function testWrongPdoErrMode() $pdo = $this->getMemorySqlitePdo(); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT); - $storage = new PdoSessionHandler($pdo); + new PdoSessionHandler($pdo); } public function testInexistentTable() diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php index fa170d17d..9ce8108da 100644 --- a/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/Tests/Session/Storage/NativeSessionStorageTest.php @@ -145,7 +145,7 @@ public function testDefaultSessionCacheLimiter() { $this->iniSet('session.cache_limiter', 'nocache'); - $storage = new NativeSessionStorage(); + new NativeSessionStorage(); $this->assertEquals('', ini_get('session.cache_limiter')); } @@ -153,7 +153,7 @@ public function testExplicitSessionCacheLimiter() { $this->iniSet('session.cache_limiter', 'nocache'); - $storage = new NativeSessionStorage(['cache_limiter' => 'public']); + new NativeSessionStorage(['cache_limiter' => 'public']); $this->assertEquals('public', ini_get('session.cache_limiter')); } From 9a98f1393ea8f7c42296c55e86b03ee683ed47cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 29 Oct 2019 14:42:59 +0100 Subject: [PATCH 36/51] [HttpFoundation] Allow to not pass a parameter to Request::isMethodSafe() --- Request.php | 7 ++----- Tests/RequestTest.php | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Request.php b/Request.php index 9af2c2825..db82ffce8 100644 --- a/Request.php +++ b/Request.php @@ -1447,14 +1447,11 @@ public function isMethod($method) * * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 * - * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. - * * @return bool */ - public function isMethodSafe(/* $andCacheable = true */) + public function isMethodSafe() { - if (!\func_num_args() || func_get_arg(0)) { - // setting $andCacheable to false should be deprecated in 4.1 + if (\func_num_args() > 0 && func_get_arg(0)) { throw new \BadMethodCallException('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is not supported.'); } diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index d56ef3147..03493b532 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -2132,7 +2132,7 @@ public function testMethodSafeChecksCacheable() $this->expectException('BadMethodCallException'); $request = new Request(); $request->setMethod('OPTIONS'); - $request->isMethodSafe(); + $request->isMethodSafe(true); } /** From e601eebb39e8a727d03873695b52a874835f46b5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Oct 2019 19:32:01 +0100 Subject: [PATCH 37/51] [HttpFoundation][FrameworkBundle] allow configuring the session handler with a DSN --- CHANGELOG.md | 1 + .../Storage/Handler/SessionHandlerFactory.php | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 Session/Storage/Handler/SessionHandlerFactory.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 577f27e6c..10eaa0ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN 4.3.0 ----- diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 000000000..41dba3a2b --- /dev/null +++ b/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection))); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection))); + case 0 === strpos($connection, 'file://'): + return new StrictSessionHandler(new NativeFileSessionHandler(substr($connection, 7))); + + case 0 === strpos($connection, 'redis://'): + case 0 === strpos($connection, 'rediss://'): + case 0 === strpos($connection, 'memcached://'): + if (!class_exists(AbstractAdapter::class)) { + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $this->dsn)); + } + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return 0 === strpos($connection, 'memcached://') ? new MemcachedSessionHandler($connection) : new RedisSessionHandler($connection); + + case 0 === strpos($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + // no break; + + case 0 === strpos($connection, 'mssql://'): + case 0 === strpos($connection, 'mysql://'): + case 0 === strpos($connection, 'mysql2://'): + case 0 === strpos($connection, 'pgsql://'): + case 0 === strpos($connection, 'postgres://'): + case 0 === strpos($connection, 'postgresql://'): + case 0 === strpos($connection, 'sqlsrv://'): + case 0 === strpos($connection, 'sqlite://'): + case 0 === strpos($connection, 'sqlite3://'): + return new PdoSessionHandler($connection); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection)); + } +} From 38f63e471cda9d37ac06e76d14c5ea2ec5887051 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 29 Oct 2019 19:32:45 +0100 Subject: [PATCH 38/51] [4.3] Remove unused local variables --- Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php index 347619e96..368af6a3e 100644 --- a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -27,7 +27,7 @@ class NativeFileSessionHandlerTest extends TestCase { public function testConstruct() { - $storage = new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler(sys_get_temp_dir())); + new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler(sys_get_temp_dir())); $this->assertEquals('user', ini_get('session.save_handler')); From 6590972d2a2642070bf34f814b9be11eef3f04f7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 30 Oct 2019 14:14:20 +0100 Subject: [PATCH 39/51] [Lock][HttpFoundation] Hot fix --- Session/Storage/Handler/SessionHandlerFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php index 41dba3a2b..e7acf5173 100644 --- a/Session/Storage/Handler/SessionHandlerFactory.php +++ b/Session/Storage/Handler/SessionHandlerFactory.php @@ -56,9 +56,10 @@ public static function createHandler($connection): AbstractSessionHandler if (!class_exists(AbstractAdapter::class)) { throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $this->dsn)); } + $handlerClass = 0 === strpos($connection, 'memcached://') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); - return 0 === strpos($connection, 'memcached://') ? new MemcachedSessionHandler($connection) : new RedisSessionHandler($connection); + return new $handlerClass($connection); case 0 === strpos($connection, 'pdo_oci://'): if (!class_exists(DriverManager::class)) { From a5d46a33e8649ba802cebe520d188b04385572a2 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sun, 3 Nov 2019 03:12:45 +0100 Subject: [PATCH 40/51] Fix MockFileSessionStorageTest::sessionDir being used after it's unset --- Tests/Session/Storage/MockFileSessionStorageTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Session/Storage/MockFileSessionStorageTest.php b/Tests/Session/Storage/MockFileSessionStorageTest.php index b316c83e6..d6bd1823f 100644 --- a/Tests/Session/Storage/MockFileSessionStorageTest.php +++ b/Tests/Session/Storage/MockFileSessionStorageTest.php @@ -41,12 +41,12 @@ protected function setUp() protected function tearDown() { - $this->sessionDir = null; - $this->storage = null; - array_map('unlink', glob($this->sessionDir.'/*.session')); + array_map('unlink', glob($this->sessionDir.'/*')); if (is_dir($this->sessionDir)) { rmdir($this->sessionDir); } + $this->sessionDir = null; + $this->storage = null; } public function testStart() From 2c367a65b40f034830cb4bb1603926c3c46fbd82 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 26 Jun 2019 16:20:24 +0200 Subject: [PATCH 41/51] [HttpFoundation] Add a way to anonymize IPs --- CHANGELOG.md | 3 ++- IpUtils.php | 32 ++++++++++++++++++++++++++++++++ Tests/IpUtilsTest.php | 26 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10eaa0ac5..e33b33ccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ CHANGELOG make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. * added `SessionHandlerFactory` to create session handlers with a DSN - + * added `IpUtils::anonymize()` to help with GDPR compliance. + 4.3.0 ----- diff --git a/IpUtils.php b/IpUtils.php index 67d13e57a..72c53a471 100644 --- a/IpUtils.php +++ b/IpUtils.php @@ -153,4 +153,36 @@ public static function checkIp6($requestIp, $ip) return self::$checkedIps[$cacheKey] = true; } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } } diff --git a/Tests/IpUtilsTest.php b/Tests/IpUtilsTest.php index d3b262e04..13b574379 100644 --- a/Tests/IpUtilsTest.php +++ b/Tests/IpUtilsTest.php @@ -101,4 +101,30 @@ public function invalidIpAddressData() 'invalid request IP with invalid proxy wildcard' => ['0.0.0.0', '*'], ]; } + + /** + * @dataProvider anonymizedIpData + */ + public function testAnonymize($ip, $expected) + { + $this->assertSame($expected, IpUtils::anonymize($ip)); + } + + public function anonymizedIpData() + { + return [ + ['192.168.1.1', '192.168.1.0'], + ['1.2.3.4', '1.2.3.0'], + ['2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603::'], + ['2a01:198:603:10:396e:4789:8e99:890f', '2a01:198:603:10::'], + ['::1', '::'], + ['0:0:0:0:0:0:0:1', '::'], + ['1:0:0:0:0:0:0:1', '1::'], + ['0:0:603:50:396e:4789:8e99:0001', '0:0:603:50::'], + ['[0:0:603:50:396e:4789:8e99:0001]', '[0:0:603:50::]'], + ['[2a01:198::3]', '[2a01:198::]'], + ['::ffff:123.234.235.236', '::ffff:123.234.235.0'], // IPv4-mapped IPv6 addresses + ['::123.234.235.236', '::123.234.235.0'], // deprecated IPv4-compatible IPv6 address + ]; + } } From 9e4b3ac8fa3348b4811674d23de32d201de225ce Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 19 Apr 2019 14:48:43 +0200 Subject: [PATCH 42/51] [HttpFoundation] fix guessing mime-types of files with leading dash --- File/MimeType/FileBinaryMimeTypeGuesser.php | 4 ++-- Tests/File/Fixtures/-test | Bin 0 -> 35 bytes Tests/File/MimeType/MimeTypeTest.php | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 Tests/File/Fixtures/-test diff --git a/File/MimeType/FileBinaryMimeTypeGuesser.php b/File/MimeType/FileBinaryMimeTypeGuesser.php index cfa76843c..7045e94df 100644 --- a/File/MimeType/FileBinaryMimeTypeGuesser.php +++ b/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -31,7 +31,7 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface * * @param string $cmd The command to run to get the mime type of a file */ - public function __construct($cmd = 'file -b --mime %s 2>/dev/null') + public function __construct($cmd = 'file -b --mime -- %s 2>/dev/null') { $this->cmd = $cmd; } @@ -80,7 +80,7 @@ public function guess($path) ob_start(); // need to use --mime instead of -i. see #6641 - passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return); if ($return > 0) { ob_end_clean(); diff --git a/Tests/File/Fixtures/-test b/Tests/File/Fixtures/-test new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/Tests/File/MimeType/MimeTypeTest.php b/Tests/File/MimeType/MimeTypeTest.php index 3960988a6..0418726b5 100644 --- a/Tests/File/MimeType/MimeTypeTest.php +++ b/Tests/File/MimeType/MimeTypeTest.php @@ -20,7 +20,16 @@ */ class MimeTypeTest extends TestCase { - protected $path; + public function testGuessWithLeadingDash() + { + $cwd = getcwd(); + chdir(__DIR__.'/../Fixtures'); + try { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess('-test')); + } finally { + chdir($cwd); + } + } public function testGuessImageWithoutExtension() { From e27325f5b25338ce222bbf4dabaae110da626601 Mon Sep 17 00:00:00 2001 From: wimg Date: Mon, 11 Nov 2019 17:54:27 +0900 Subject: [PATCH 43/51] [HttpFoundation][Lock] Connection parameter corrected --- Session/Storage/Handler/SessionHandlerFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php index e7acf5173..00cf1521a 100644 --- a/Session/Storage/Handler/SessionHandlerFactory.php +++ b/Session/Storage/Handler/SessionHandlerFactory.php @@ -54,7 +54,7 @@ public static function createHandler($connection): AbstractSessionHandler case 0 === strpos($connection, 'rediss://'): case 0 === strpos($connection, 'memcached://'): if (!class_exists(AbstractAdapter::class)) { - throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $this->dsn)); + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $storage)); } $handlerClass = 0 === strpos($connection, 'memcached://') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); From 55b22cc56de073a9a93f5ebd0ebb4d01d346c5f1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Nov 2019 14:11:43 +0100 Subject: [PATCH 44/51] [HttpFoundation] fix typo --- Session/Storage/Handler/SessionHandlerFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Storage/Handler/SessionHandlerFactory.php b/Session/Storage/Handler/SessionHandlerFactory.php index 00cf1521a..f4feeac09 100644 --- a/Session/Storage/Handler/SessionHandlerFactory.php +++ b/Session/Storage/Handler/SessionHandlerFactory.php @@ -54,7 +54,7 @@ public static function createHandler($connection): AbstractSessionHandler case 0 === strpos($connection, 'rediss://'): case 0 === strpos($connection, 'memcached://'): if (!class_exists(AbstractAdapter::class)) { - throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $storage)); + throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); } $handlerClass = 0 === strpos($connection, 'memcached://') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); From c9425bae96ae95449d59f8286529cf144bb228ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Broutier?= Date: Sat, 16 Nov 2019 12:20:14 +0100 Subject: [PATCH 45/51] Fix MySQL column type definition. Fix wrong MySQL column type definition causing Numeric value out of range exception. Ref #34409 --- Session/Storage/Handler/PdoSessionHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index f9e5d1e8f..c9d47b6ed 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -218,7 +218,7 @@ public function createTable() // - trailing space removal // - case-insensitivity // - language processing like é == e - $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; break; case 'sqlite': $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; From 9088e3f338e443869737d53af064fad9614ecc7d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 16 Nov 2019 16:17:02 +0100 Subject: [PATCH 46/51] [HttpFoundation] fix docblock --- Session/Storage/Handler/RedisSessionHandler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php index 46ca6796d..8ebc5bd8e 100644 --- a/Session/Storage/Handler/RedisSessionHandler.php +++ b/Session/Storage/Handler/RedisSessionHandler.php @@ -34,8 +34,7 @@ class RedisSessionHandler extends AbstractSessionHandler * List of available options: * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server. * - * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy $redis - * @param array $options An associative array of options + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis * * @throws \InvalidArgumentException When unsupported client or options are passed */ From ef5fed4469fb079c0b188ab0401cd1b28752865b Mon Sep 17 00:00:00 2001 From: Mark Beech Date: Wed, 13 Nov 2019 20:07:22 +0000 Subject: [PATCH 47/51] [HttpFoundation] Allow redirecting to URLs that contain a semicolon --- RedirectResponse.php | 2 +- Tests/RedirectResponseTest.php | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/RedirectResponse.php b/RedirectResponse.php index 4e3cb4f77..a19efba3e 100644 --- a/RedirectResponse.php +++ b/RedirectResponse.php @@ -93,7 +93,7 @@ public function setTargetUrl($url) - + Redirecting to %1$s diff --git a/Tests/RedirectResponseTest.php b/Tests/RedirectResponseTest.php index e1ff3bf2b..2bbf5aa1a 100644 --- a/Tests/RedirectResponseTest.php +++ b/Tests/RedirectResponseTest.php @@ -20,10 +20,7 @@ public function testGenerateMetaRedirect() { $response = new RedirectResponse('foo.bar'); - $this->assertEquals(1, preg_match( - '##', - preg_replace(['/\s+/', '/\'/'], [' ', '"'], $response->getContent()) - )); + $this->assertRegExp('##', preg_replace('/\s+/', ' ', $response->getContent())); } public function testRedirectResponseConstructorNullUrl() From 0c5217a9050712a1e66f851d04962abf8f2c6fc4 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 16 Nov 2019 03:22:35 +0600 Subject: [PATCH 48/51] [HttpFoundation] Added possibility to configure expiration time in redis session handler --- .../Storage/Handler/RedisSessionHandler.php | 15 +++++++--- .../AbstractRedisSessionHandlerTestCase.php | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Session/Storage/Handler/RedisSessionHandler.php b/Session/Storage/Handler/RedisSessionHandler.php index 40c209341..f11c8c4d6 100644 --- a/Session/Storage/Handler/RedisSessionHandler.php +++ b/Session/Storage/Handler/RedisSessionHandler.php @@ -30,9 +30,15 @@ class RedisSessionHandler extends AbstractSessionHandler */ private $prefix; + /** + * @var int Time to live in seconds + */ + private $ttl; + /** * List of available options: - * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server. + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. * * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy $redis * @@ -51,12 +57,13 @@ public function __construct($redis, array $options = []) throw new \InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis))); } - if ($diff = array_diff(array_keys($options), ['prefix'])) { + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff))); } $this->redis = $redis; $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? (int) ini_get('session.gc_maxlifetime'); } /** @@ -72,7 +79,7 @@ protected function doRead($sessionId): string */ protected function doWrite($sessionId, $data): bool { - $result = $this->redis->setEx($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime'), $data); + $result = $this->redis->setEx($this->prefix.$sessionId, $this->ttl, $data); return $result && !$result instanceof ErrorInterface; } @@ -108,6 +115,6 @@ public function gc($maxlifetime): bool */ public function updateTimestamp($sessionId, $data) { - return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime')); + return (bool) $this->redis->expire($this->prefix.$sessionId, $this->ttl); } } diff --git a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index 6a15a0687..8828be666 100644 --- a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -139,7 +139,37 @@ public function getOptionFixtures(): array { return [ [['prefix' => 'session'], true], + [['ttl' => 1000], true], + [['prefix' => 'sfs', 'ttl' => 1000], true], [['prefix' => 'sfs', 'foo' => 'bar'], false], + [['ttl' => 'sfs', 'foo' => 'bar'], false], + ]; + } + + /** + * @dataProvider getTtlFixtures + */ + public function testUseTtlOption(int $ttl) + { + $options = [ + 'prefix' => self::PREFIX, + 'ttl' => $ttl, + ]; + + $handler = new RedisSessionHandler($this->redisClient, $options); + $handler->write('id', 'data'); + $redisTtl = $this->redisClient->ttl(self::PREFIX.'id'); + + $this->assertLessThan($redisTtl, $ttl - 5); + $this->assertGreaterThan($redisTtl, $ttl + 5); + } + + public function getTtlFixtures(): array + { + return [ + ['ttl' => 5000], + ['ttl' => 120], + ['ttl' => 60], ]; } } From f7efd0b387b7bdbfe0fd1e38fe6b7d4a812b4e39 Mon Sep 17 00:00:00 2001 From: Roy-Orbison Date: Wed, 20 Nov 2019 15:04:29 +1030 Subject: [PATCH 49/51] Simpler example for Apache basic auth workaround Uses a simpler regex and existing back-reference instead of reading header twice. --- ServerBag.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ServerBag.php b/ServerBag.php index 4c82b1774..f3b640234 100644 --- a/ServerBag.php +++ b/ServerBag.php @@ -46,13 +46,13 @@ public function getHeaders() /* * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default * For this workaround to work, add these lines to your .htaccess file: - * RewriteCond %{HTTP:Authorization} ^(.+)$ - * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] * * A sample .htaccess file: * RewriteEngine On - * RewriteCond %{HTTP:Authorization} ^(.+)$ - * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] * RewriteCond %{REQUEST_FILENAME} !-f * RewriteRule ^(.*)$ app.php [QSA,L] */ From cc09809aa4182d5d062cde2c5836c5281f4e96eb Mon Sep 17 00:00:00 2001 From: Quentin Favrie Date: Mon, 25 Nov 2019 11:31:45 +0100 Subject: [PATCH 50/51] [HttpFoundation] Update CHANGELOG for PdoSessionHandler BC BREAK in 4.4 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e33b33ccb..3fa73a26a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ CHANGELOG * passing arguments to `Request::isMethodSafe()` is deprecated. * `ApacheRequest` is deprecated, use the `Request` class instead. * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, + make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to + update your database. * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. From d2d0cfe8e319d9df44c4cca570710fcf221d4593 Mon Sep 17 00:00:00 2001 From: Thomas Bisignani Date: Thu, 28 Nov 2019 13:52:59 +0100 Subject: [PATCH 51/51] [HttpFoundation] Fixed typo --- BinaryFileResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php index f43114111..ea7ac8469 100644 --- a/BinaryFileResponse.php +++ b/BinaryFileResponse.php @@ -348,7 +348,7 @@ public static function trustXSendfileTypeHeader() } /** - * If this is set to true, the file will be unlinked after the request is send + * If this is set to true, the file will be unlinked after the request is sent * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. * * @param bool $shouldDelete