diff --git a/CHANGELOG.md b/CHANGELOG.md index 374c31889..4fc5cbf7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ CHANGELOG ========= +8.0 +--- + + * Drop HTTP method override support for methods GET, HEAD, CONNECT and TRACE + * Add argument `$subtypeFallback` to `Request::getFormat()` + * Remove the following deprecated session options from `NativeSessionStorage`: `referer_check`, `use_only_cookies`, `use_trans_sid`, `sid_length`, `sid_bits_per_character`, `trans_sid_hosts`, `trans_sid_tags` + * Trigger PHP warning when using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead + * Add arguments `$v4Bytes` and `$v6Bytes` to `IpUtils::anonymize()` + * Add argument `$partitioned` to `ResponseHeaderBag::clearCookie()` + * Add argument `$expiration` to `UriSigner::sign()` + * Remove `Request::get()`, use properties `->attributes`, `query` or `request` directly instead + * Remove accepting null `$format` argument to `Request::setFormat()` + +7.4 +--- + + * Add `#[WithHttpStatus]` to define status codes: 404 for `SignedUriException` and 403 for `ExpiredSignedUriException` + * Add support for the `QUERY` HTTP method + * Add support for structured MIME suffix + * Add `Request::set/getAllowedHttpMethodOverride()` to list which HTTP methods can be overridden + * Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead + * Deprecate method `Request::get()`, use properties `->attributes`, `query` or `request` directly instead + * Make `Request::createFromGlobals()` parse the body of PUT, DELETE, PATCH and QUERY requests + * Deprecate HTTP method override for methods GET, HEAD, CONNECT and TRACE; it will be ignored in Symfony 8.0 + * Deprecate accepting null `$format` argument to `Request::setFormat()` + 7.3 --- diff --git a/Exception/ExpiredSignedUriException.php b/Exception/ExpiredSignedUriException.php index 613e08ef4..46907a3a3 100644 --- a/Exception/ExpiredSignedUriException.php +++ b/Exception/ExpiredSignedUriException.php @@ -11,9 +11,12 @@ namespace Symfony\Component\HttpFoundation\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * @author Kevin Bond */ +#[WithHttpStatus(403)] final class ExpiredSignedUriException extends SignedUriException { /** diff --git a/Exception/SignedUriException.php b/Exception/SignedUriException.php index 17b729d31..09c80657a 100644 --- a/Exception/SignedUriException.php +++ b/Exception/SignedUriException.php @@ -11,9 +11,12 @@ namespace Symfony\Component\HttpFoundation\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * @author Kevin Bond */ +#[WithHttpStatus(404)] abstract class SignedUriException extends \RuntimeException implements ExceptionInterface { } diff --git a/File/File.php b/File/File.php index 2194c178a..1200a8dfd 100644 --- a/File/File.php +++ b/File/File.php @@ -96,7 +96,7 @@ public function move(string $directory, ?string $name = null): self throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); } - @chmod($target, 0666 & ~umask()); + @chmod($target, 0o666 & ~umask()); return $target; } @@ -114,10 +114,11 @@ public function getContent(): string protected function getTargetFile(string $directory, ?string $name = null): self { - if (!is_dir($directory)) { - if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { - throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory)); + if (!is_dir($directory) && !@mkdir($directory, 0o777, true) && !is_dir($directory)) { + if (is_file($directory)) { + throw new FileException(\sprintf('Unable to create the "%s" directory: a similarly-named file exists.', $directory)); } + throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory)); } elseif (!is_writable($directory)) { throw new FileException(\sprintf('Unable to write in the "%s" directory.', $directory)); } diff --git a/File/UploadedFile.php b/File/UploadedFile.php index a27a56f96..53dce7de5 100644 --- a/File/UploadedFile.php +++ b/File/UploadedFile.php @@ -197,7 +197,7 @@ public function move(string $directory, ?string $name = null): File throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); } - @chmod($target, 0666 & ~umask()); + @chmod($target, 0o666 & ~umask()); return $target; } diff --git a/HeaderUtils.php b/HeaderUtils.php index a7079be9a..37953af4f 100644 --- a/HeaderUtils.php +++ b/HeaderUtils.php @@ -164,7 +164,7 @@ public static function unquote(string $s): string */ public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string { - if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE], true)) { throw new \InvalidArgumentException(\sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); } diff --git a/InputBag.php b/InputBag.php index 7411d755c..8c153dc30 100644 --- a/InputBag.php +++ b/InputBag.php @@ -17,6 +17,8 @@ /** * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. * + * @template TInput of string|int|float|bool|null + * * @author Saif Eddin Gmati */ final class InputBag extends ParameterBag @@ -24,7 +26,11 @@ final class InputBag extends ParameterBag /** * Returns a scalar input value by name. * - * @param string|int|float|bool|null $default The default value if the input key does not exist + * @template TDefault of string|int|float|bool|null + * + * @param TDefault $default The default value if the input key does not exist + * + * @return TDefault|TInput * * @throws BadRequestException if the input contains a non-scalar value */ diff --git a/IpUtils.php b/IpUtils.php index f67e314ab..b7a34675e 100644 --- a/IpUtils.php +++ b/IpUtils.php @@ -183,11 +183,8 @@ public static function checkIp6(string $requestIp, string $ip): bool * @param int<0, 4> $v4Bytes * @param int<0, 16> $v6Bytes */ - public static function anonymize(string $ip/* , int $v4Bytes = 1, int $v6Bytes = 8 */): string + public static function anonymize(string $ip, int $v4Bytes = 1, int $v6Bytes = 8): string { - $v4Bytes = 1 < \func_num_args() ? func_get_arg(1) : 1; - $v6Bytes = 2 < \func_num_args() ? func_get_arg(2) : 8; - if ($v4Bytes < 0 || $v6Bytes < 0) { throw new \InvalidArgumentException('Cannot anonymize less than 0 bytes.'); } diff --git a/Request.php b/Request.php index c959768ab..9d74e2fa9 100644 --- a/Request.php +++ b/Request.php @@ -62,6 +62,7 @@ class Request public const METHOD_OPTIONS = 'OPTIONS'; public const METHOD_TRACE = 'TRACE'; public const METHOD_CONNECT = 'CONNECT'; + public const METHOD_QUERY = 'QUERY'; /** * @var string[] @@ -80,6 +81,13 @@ class Request protected static bool $httpMethodParameterOverride = false; + /** + * The HTTP methods that can be overridden. + * + * @var uppercase-string[]|null + */ + protected static ?array $allowedHttpMethodOverride = null; + /** * Custom parameters. */ @@ -94,6 +102,8 @@ class Request /** * Query string parameters ($_GET). + * + * @var InputBag */ public InputBag $query; @@ -109,6 +119,8 @@ class Request /** * Cookies ($_COOKIE). + * + * @var InputBag */ public InputBag $cookies; @@ -194,6 +206,28 @@ class Request self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', ]; + /** + * This mapping is used when no exact MIME match is found in $formats. + * + * It enables mappings like application/soap+xml -> xml. + * + * @see https://datatracker.ietf.org/doc/html/rfc6839 + * @see https://datatracker.ietf.org/doc/html/rfc7303 + * @see https://www.iana.org/assignments/media-types/media-types.xhtml + */ + private const STRUCTURED_SUFFIX_FORMATS = [ + 'json' => 'json', + 'xml' => 'xml', + 'xhtml' => 'html', + 'cbor' => 'cbor', + 'zip' => 'zip', + 'ber' => 'asn1', + 'der' => 'asn1', + 'tlv' => 'tlv', + 'wbxml' => 'xml', + 'yaml' => 'yaml', + ]; + private bool $isIisRewrite = false; /** @@ -251,16 +285,18 @@ public function initialize(array $query = [], array $request = [], array $attrib */ public static function createFromGlobals(): static { - $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + if (!\in_array($_SERVER['REQUEST_METHOD'] ?? null, ['PUT', 'DELETE', 'PATCH', 'QUERY'], true)) { + return self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + } - if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') - && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'], true) - ) { - parse_str($request->getContent(), $data); - $request->request = new InputBag($data); + try { + [$post, $files] = request_parse_body(); + } catch (\RequestParseBodyException) { + $post = $_POST; + $files = $_FILES; } - return $request; + return self::createRequestFromFactory($_GET, $post, [], $_COOKIE, $files, $_SERVER); } /** @@ -361,6 +397,7 @@ public static function create(string $uri, string $method = 'GET', array $parame case 'POST': case 'PUT': case 'DELETE': + case 'QUERY': if (!isset($server['CONTENT_TYPE'])) { $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; } @@ -451,8 +488,8 @@ public function duplicate(?array $query = null, ?array $request = null, ?array $ $dup->method = null; $dup->format = null; - if (!$dup->get('_format') && $this->get('_format')) { - $dup->attributes->set('_format', $this->get('_format')); + if (!$dup->attributes->has('_format') && $this->attributes->has('_format')) { + $dup->attributes->set('_format', $this->attributes->get('_format')); } if (!$dup->getRequestFormat(null)) { @@ -654,31 +691,31 @@ public static function getHttpMethodParameterOverride(): bool } /** - * Gets a "parameter" value from any bag. + * Sets the list of HTTP methods that can be overridden. * - * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the - * flexibility in controllers, it is better to explicitly get request parameters from the appropriate - * public property instead (attributes, query, request). + * Set to null to allow all methods to be overridden (default). Set to an + * empty array to disallow overrides entirely. Otherwise, provide the list + * of uppercased method names that are allowed. * - * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST - * - * @internal use explicit input sources instead + * @param uppercase-string[]|null $methods */ - public function get(string $key, mixed $default = null): mixed + public static function setAllowedHttpMethodOverride(?array $methods): void { - if ($this !== $result = $this->attributes->get($key, $this)) { - return $result; - } - - if ($this->query->has($key)) { - return $this->query->all()[$key]; + if (array_intersect($methods ?? [], ['GET', 'HEAD', 'CONNECT', 'TRACE'])) { + throw new \InvalidArgumentException('The HTTP methods "GET", "HEAD", "CONNECT", and "TRACE" cannot be overridden.'); } - if ($this->request->has($key)) { - return $this->request->all()[$key]; - } + self::$allowedHttpMethodOverride = $methods; + } - return $default; + /** + * Gets the list of HTTP methods that can be overridden. + * + * @return uppercase-string[]|null + */ + public static function getAllowedHttpMethodOverride(): ?array + { + return self::$allowedHttpMethodOverride; } /** @@ -1075,7 +1112,7 @@ public function isSecure(): bool $https = $this->server->get('HTTPS'); - return $https && 'off' !== strtolower($https); + return $https && (!\is_string($https) || 'off' !== strtolower($https)); } /** @@ -1092,10 +1129,8 @@ public function getHost(): string { if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { $host = $host[0]; - } elseif (!$host = $this->headers->get('HOST')) { - if (!$host = $this->server->get('SERVER_NAME')) { - $host = $this->server->get('SERVER_ADDR', ''); - } + } else { + $host = $this->headers->get('HOST') ?: $this->server->get('SERVER_NAME') ?: $this->server->get('SERVER_ADDR', ''); } // trim and remove port number from host @@ -1168,7 +1203,7 @@ public function getMethod(): string $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); - if ('POST' !== $this->method) { + if ('POST' !== $this->method || !(self::$allowedHttpMethodOverride ?? true)) { return $this->method; } @@ -1184,11 +1219,15 @@ public function getMethod(): string $method = strtoupper($method); - if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { - return $this->method = $method; + if (\in_array($method, ['GET', 'HEAD', 'CONNECT', 'TRACE'], true)) { + return $this->method; } - if (!preg_match('/^[A-Z]++$/D', $method)) { + if (self::$allowedHttpMethodOverride && !\in_array($method, self::$allowedHttpMethodOverride, true)) { + return $this->method; + } + + if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) { throw new SuspiciousOperationException('Invalid HTTP method override.'); } @@ -1233,8 +1272,20 @@ public static function getMimeTypes(string $format): array /** * Gets the format associated with the mime type. + * + * Resolution order: + * 1) Exact match on the full MIME type (e.g. "application/json"). + * 2) Match on the canonical MIME type (i.e. before the first ";" parameter). + * 3) If the type is "application/*+suffix", use the structured syntax suffix + * mapping (e.g. "application/foo+json" → "json"), when available. + * 4) If $subtypeFallback is true and no match was found: + * - return the MIME subtype (without "x-" prefix), provided it does not + * contain a "+" (e.g. "application/x-yaml" → "yaml", "text/csv" → "csv"). + * + * @param string|null $mimeType The mime type to check + * @param bool $subtypeFallback Whether to fall back to the subtype if no exact match is found */ - public function getFormat(?string $mimeType): ?string + public function getFormat(?string $mimeType, bool $subtypeFallback = false): ?string { $canonicalMimeType = null; if ($mimeType && false !== $pos = strpos($mimeType, ';')) { @@ -1261,6 +1312,27 @@ public function getFormat(?string $mimeType): ?string return $format; } + if (!$canonicalMimeType ??= $mimeType) { + return null; + } + + if (str_starts_with($canonicalMimeType, 'application/') && str_contains($canonicalMimeType, '+')) { + $suffix = substr(strrchr($canonicalMimeType, '+'), 1); + if (isset(self::STRUCTURED_SUFFIX_FORMATS[$suffix])) { + return self::STRUCTURED_SUFFIX_FORMATS[$suffix]; + } + } + + if ($subtypeFallback && str_contains($canonicalMimeType, '/')) { + [, $subtype] = explode('/', $canonicalMimeType, 2); + if (str_starts_with($subtype, 'x-')) { + $subtype = substr($subtype, 2); + } + if (!str_contains($subtype, '+')) { + return $subtype; + } + } + return null; } @@ -1269,13 +1341,13 @@ public function getFormat(?string $mimeType): ?string * * @param string|string[] $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) */ - public function setFormat(?string $format, string|array $mimeTypes): void + public function setFormat(string $format, string|array $mimeTypes): void { if (null === static::$formats) { static::initializeFormats(); } - static::$formats[$format ?? ''] = (array) $mimeTypes; + static::$formats[$format] = (array) $mimeTypes; } /** @@ -1367,7 +1439,7 @@ public function isMethod(string $method): bool */ public function isMethodSafe(): bool { - return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); + return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE', 'QUERY'], true); } /** @@ -1375,7 +1447,7 @@ public function isMethodSafe(): bool */ public function isMethodIdempotent(): bool { - return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); + return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE', 'QUERY'], true); } /** @@ -1385,7 +1457,7 @@ public function isMethodIdempotent(): bool */ public function isMethodCacheable(): bool { - return \in_array($this->getMethod(), ['GET', 'HEAD']); + return \in_array($this->getMethod(), ['GET', 'HEAD', 'QUERY'], true); } /** @@ -1421,10 +1493,8 @@ public function getProtocolVersion(): ?string */ public function getContent(bool $asResource = false) { - $currentContentIsResource = \is_resource($this->content); - - if (true === $asResource) { - if ($currentContentIsResource) { + if ($asResource) { + if (\is_resource($this->content)) { rewind($this->content); return $this->content; @@ -1444,7 +1514,7 @@ public function getContent(bool $asResource = false) return fopen('php://input', 'r'); } - if ($currentContentIsResource) { + if (\is_resource($this->content)) { rewind($this->content); return stream_get_contents($this->content); @@ -1932,6 +2002,14 @@ protected static function initializeFormats(): void 'atom' => ['application/atom+xml'], 'rss' => ['application/rss+xml'], 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'], + 'soap' => ['application/soap+xml'], + 'problem' => ['application/problem+json'], + 'hal' => ['application/hal+json', 'application/hal+xml'], + 'jsonapi' => ['application/vnd.api+json'], + 'yaml' => ['text/yaml', 'application/x-yaml'], + 'wbxml' => ['application/vnd.wap.wbxml'], + 'pdf' => ['application/pdf'], + 'csv' => ['text/csv'], ]; } diff --git a/Response.php b/Response.php index 638b5bf60..7cd3b8770 100644 --- a/Response.php +++ b/Response.php @@ -261,7 +261,7 @@ public function prepare(Request $request): static } // Fix Content-Type - $charset = $this->charset ?: 'UTF-8'; + $charset = $this->charset ?: 'utf-8'; if (!$headers->has('Content-Type')) { $headers->set('Content-Type', 'text/html; charset='.$charset); } elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) { @@ -317,6 +317,11 @@ public function sendHeaders(?int $statusCode = null): static { // headers have already been sent by the developer if (headers_sent()) { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + $statusCode ??= $this->statusCode; + header(\sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode); + } + return $this; } @@ -539,7 +544,7 @@ public function getCharset(): ?string */ public function isCacheable(): bool { - if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { + if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410], true)) { return false; } @@ -1248,7 +1253,7 @@ public function isNotFound(): bool */ public function isRedirect(?string $location = null): bool { - return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); + return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308], true) && (null === $location ?: $location == $this->headers->get('Location')); } /** @@ -1258,7 +1263,7 @@ public function isRedirect(?string $location = null): bool */ public function isEmpty(): bool { - return \in_array($this->statusCode, [204, 304]); + return \in_array($this->statusCode, [204, 304], true); } /** diff --git a/ResponseHeaderBag.php b/ResponseHeaderBag.php index 4e089fe60..5f11ffd88 100644 --- a/ResponseHeaderBag.php +++ b/ResponseHeaderBag.php @@ -194,7 +194,7 @@ public function removeCookie(string $name, ?string $path = '/', ?string $domain */ public function getCookies(string $format = self::COOKIES_FLAT): array { - if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { + if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY], true)) { throw new \InvalidArgumentException(\sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); } @@ -216,13 +216,9 @@ public function getCookies(string $format = self::COOKIES_FLAT): array /** * Clears a cookie in the browser. - * - * @param bool $partitioned */ - public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */): void + public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null, bool $partitioned = false): void { - $partitioned = 6 < \func_num_args() ? func_get_arg(6) : false; - $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned)); } diff --git a/Session/Storage/Handler/NativeFileSessionHandler.php b/Session/Storage/Handler/NativeFileSessionHandler.php index 284cd869d..81e97be94 100644 --- a/Session/Storage/Handler/NativeFileSessionHandler.php +++ b/Session/Storage/Handler/NativeFileSessionHandler.php @@ -41,7 +41,7 @@ public function __construct(?string $savePath = null) $baseDir = ltrim(strrchr($savePath, ';'), ';'); } - if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0o777, true) && !is_dir($baseDir)) { throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $baseDir)); } diff --git a/Session/Storage/Handler/PdoSessionHandler.php b/Session/Storage/Handler/PdoSessionHandler.php index 217656958..28cb96750 100644 --- a/Session/Storage/Handler/PdoSessionHandler.php +++ b/Session/Storage/Handler/PdoSessionHandler.php @@ -228,12 +228,7 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); } - if (class_exists(PrimaryKeyConstraint::class)) { - $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true)); - } else { - $table->setPrimaryKey([$this->idCol]); - } - + $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true)); $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx'); } diff --git a/Session/Storage/MockFileSessionStorage.php b/Session/Storage/MockFileSessionStorage.php index c230c701a..d7a979395 100644 --- a/Session/Storage/MockFileSessionStorage.php +++ b/Session/Storage/MockFileSessionStorage.php @@ -34,7 +34,7 @@ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID { $savePath ??= sys_get_temp_dir(); - if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + if (!is_dir($savePath) && !@mkdir($savePath, 0o777, true) && !is_dir($savePath)) { throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $savePath)); } @@ -103,7 +103,7 @@ public function save(): void $this->data = $data; } - // this is needed when the session object is re-used across multiple requests + // this is needed when the session object is reused across multiple requests // in functional tests. $this->started = false; } diff --git a/Session/Storage/NativeSessionStorage.php b/Session/Storage/NativeSessionStorage.php index 5c0851347..4077160e0 100644 --- a/Session/Storage/NativeSessionStorage.php +++ b/Session/Storage/NativeSessionStorage.php @@ -62,16 +62,9 @@ class NativeSessionStorage implements SessionStorageInterface * gc_probability, "1" * lazy_write, "1" * name, "PHPSESSID" - * referer_check, "" (deprecated since Symfony 7.2, to be removed in Symfony 8.0) * serialize_handler, "php" * use_strict_mode, "1" * use_cookies, "1" - * use_only_cookies, "1" (deprecated since Symfony 7.2, to be removed in Symfony 8.0) - * use_trans_sid, "0" (deprecated since Symfony 7.2, to be removed in Symfony 8.0) - * sid_length, "32" (@deprecated since Symfony 7.2, to be removed in 8.0) - * sid_bits_per_character, "5" (@deprecated since Symfony 7.2, to be removed in 8.0) - * trans_sid_hosts, $_SERVER['HTTP_HOST'] (deprecated since Symfony 7.2, to be removed in Symfony 8.0) - * trans_sid_tags, "a=href,area=href,frame=src,form=" (deprecated since Symfony 7.2, to be removed in Symfony 8.0) */ public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null) { @@ -122,16 +115,12 @@ public function start(): bool * * ---------- Part 1 * - * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6. + * The part `[a-zA-Z0-9,-]` corresponds to the character range when PHP's `session.sid_bits_per_character` is set to 6. * See https://php.net/session.configuration#ini.session.sid-bits-per-character - * Allowed values are integers such as: - * - 4 for range `a-f0-9` - * - 5 for range `a-v0-9` (@deprecated since Symfony 7.2, it will default to 4 and the option will be ignored in Symfony 8.0) - * - 6 for range `a-zA-Z0-9,-` (@deprecated since Symfony 7.2, it will default to 4 and the option will be ignored in Symfony 8.0) * * ---------- Part 2 * - * The part `{22,250}` is related to the PHP ini directive `session.sid_length`. + * The part `{22,250}` defines the acceptable length range for session IDs. * See https://php.net/session.configuration#ini.session.sid-length * Allowed values are integers between 22 and 256, but we use 250 for the max. * @@ -139,8 +128,6 @@ public function start(): bool * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255. * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250. * - * This is @deprecated since Symfony 7.2, the sid length will default to 32 and the option will be ignored in Symfony 8.0. - * * ---------- Conclusion * * The parts 1 and 2 prevent the warning below: @@ -323,17 +310,11 @@ public function setOptions(array $options): void 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', 'gc_divisor', 'gc_maxlifetime', 'gc_probability', - 'lazy_write', 'name', 'referer_check', + 'lazy_write', 'name', 'serialize_handler', 'use_strict_mode', 'use_cookies', - 'use_only_cookies', 'use_trans_sid', - 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', ]); foreach ($options as $key => $value) { - if (\in_array($key, ['referer_check', 'use_only_cookies', 'use_trans_sid', 'trans_sid_hosts', 'trans_sid_tags', 'sid_length', 'sid_bits_per_character'], true)) { - trigger_deprecation('symfony/http-foundation', '7.2', 'NativeSessionStorage\'s "%s" option is deprecated and will be ignored in Symfony 8.0.', $key); - } - if (isset($validOptions[$key])) { if ('cookie_secure' === $key && 'auto' === $value) { continue; diff --git a/Tests/AcceptHeaderItemTest.php b/Tests/AcceptHeaderItemTest.php index 7ec8c30fb..0e8e6fcae 100644 --- a/Tests/AcceptHeaderItemTest.php +++ b/Tests/AcceptHeaderItemTest.php @@ -11,14 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\AcceptHeaderItem; class AcceptHeaderItemTest extends TestCase { - /** - * @dataProvider provideFromStringData - */ + #[DataProvider('provideFromStringData')] public function testFromString($string, $value, array $attributes) { $item = AcceptHeaderItem::fromString($string); @@ -48,9 +47,7 @@ public static function provideFromStringData() ]; } - /** - * @dataProvider provideToStringData - */ + #[DataProvider('provideToStringData')] public function testToString($value, array $attributes, $string) { $item = new AcceptHeaderItem($value, $attributes); diff --git a/Tests/AcceptHeaderTest.php b/Tests/AcceptHeaderTest.php index d64b9c29c..ada370250 100644 --- a/Tests/AcceptHeaderTest.php +++ b/Tests/AcceptHeaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\AcceptHeader; use Symfony\Component\HttpFoundation\AcceptHeaderItem; @@ -23,9 +24,7 @@ public function testFirst() $this->assertSame('text/html', $header->first()->getValue()); } - /** - * @dataProvider provideFromStringData - */ + #[DataProvider('provideFromStringData')] public function testFromString($string, array $items) { $header = AcceptHeader::fromString($string); @@ -50,9 +49,7 @@ public static function provideFromStringData() ]; } - /** - * @dataProvider provideToStringData - */ + #[DataProvider('provideToStringData')] public function testToString(array $items, $string) { $header = new AcceptHeader($items); @@ -69,9 +66,7 @@ public static function provideToStringData() ]; } - /** - * @dataProvider provideFilterData - */ + #[DataProvider('provideFilterData')] public function testFilter($string, $filter, array $values) { $header = AcceptHeader::fromString($string)->filter($filter); @@ -85,9 +80,7 @@ public static function provideFilterData() ]; } - /** - * @dataProvider provideSortingData - */ + #[DataProvider('provideSortingData')] public function testSorting($string, array $values) { $header = AcceptHeader::fromString($string); @@ -105,9 +98,7 @@ public static function provideSortingData() ]; } - /** - * @dataProvider provideDefaultValueData - */ + #[DataProvider('provideDefaultValueData')] public function testDefaultValue($acceptHeader, $value, $expectedQuality) { $header = AcceptHeader::fromString($acceptHeader); diff --git a/Tests/BinaryFileResponseTest.php b/Tests/BinaryFileResponseTest.php index 7627cd5ec..2e31de8e2 100644 --- a/Tests/BinaryFileResponseTest.php +++ b/Tests/BinaryFileResponseTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\Stream; @@ -79,9 +80,7 @@ public function testSetContentDispositionGeneratesSafeFallbackFilenameForWrongly $this->assertSame('attachment; filename=f__.html; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition')); } - /** - * @dataProvider provideRanges - */ + #[DataProvider('provideRanges')] public function testRequests($requestRange, $offset, $length, $responseRange) { $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); @@ -111,9 +110,7 @@ public function testRequests($requestRange, $offset, $length, $responseRange) $this->assertSame((string) $length, $response->headers->get('Content-Length')); } - /** - * @dataProvider provideRanges - */ + #[DataProvider('provideRanges')] public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) { $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); @@ -173,9 +170,7 @@ public function testRangeRequestsWithoutLastModifiedDate() $this->assertNull($response->headers->get('Content-Range')); } - /** - * @dataProvider provideFullFileRanges - */ + #[DataProvider('provideFullFileRanges')] public function testFullFileRequests($requestRange) { $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); @@ -243,9 +238,7 @@ public function testUnpreparedResponseSendsFullFile() $this->assertEquals(200, $response->getStatusCode()); } - /** - * @dataProvider provideInvalidRanges - */ + #[DataProvider('provideInvalidRanges')] public function testInvalidRequests($requestRange) { $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); @@ -270,9 +263,7 @@ public static function provideInvalidRanges() ]; } - /** - * @dataProvider provideXSendfileFiles - */ + #[DataProvider('provideXSendfileFiles')] public function testXSendfile($file) { $request = Request::create('/'); @@ -296,9 +287,7 @@ public static function provideXSendfileFiles() ]; } - /** - * @dataProvider getSampleXAccelMappings - */ + #[DataProvider('getSampleXAccelMappings')] public function testXAccelMapping($realpath, $mapping, $virtual) { $request = Request::create('/'); diff --git a/Tests/CookieTest.php b/Tests/CookieTest.php index b55d29cc7..8ed32a160 100644 --- a/Tests/CookieTest.php +++ b/Tests/CookieTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; @@ -19,9 +21,8 @@ * * @author John Kary * @author Hugo Hamon - * - * @group time-sensitive */ +#[Group('time-sensitive')] class CookieTest extends TestCase { public static function namesWithSpecialCharacters() @@ -38,27 +39,21 @@ public static function namesWithSpecialCharacters() ]; } - /** - * @dataProvider namesWithSpecialCharacters - */ + #[DataProvider('namesWithSpecialCharacters')] public function testInstantiationThrowsExceptionIfRawCookieNameContainsSpecialCharacters($name) { $this->expectException(\InvalidArgumentException::class); Cookie::create($name, null, 0, null, null, null, false, true); } - /** - * @dataProvider namesWithSpecialCharacters - */ + #[DataProvider('namesWithSpecialCharacters')] public function testWithRawThrowsExceptionIfCookieNameContainsSpecialCharacters($name) { $this->expectException(\InvalidArgumentException::class); Cookie::create($name)->withRaw(); } - /** - * @dataProvider namesWithSpecialCharacters - */ + #[DataProvider('namesWithSpecialCharacters')] public function testInstantiationSucceedNonRawCookieNameContainsSpecialCharacters($name) { $this->assertInstanceOf(Cookie::class, Cookie::create($name)); diff --git a/Tests/EventStreamResponseTest.php b/Tests/EventStreamResponseTest.php index a941bc40e..b77040d39 100644 --- a/Tests/EventStreamResponseTest.php +++ b/Tests/EventStreamResponseTest.php @@ -50,14 +50,14 @@ public function testStreamSingleEvent() }); $expected = <<assertSameResponseContent($expected, $response); } @@ -77,17 +77,17 @@ public function testStreamEventsAndData() }); $expected = <<assertSameResponseContent($expected, $response); } @@ -101,16 +101,16 @@ public function testStreamEventsWithRetryFallback() }, retry: 1500); $expected = <<assertSameResponseContent($expected, $response); } diff --git a/Tests/File/FileTest.php b/Tests/File/FileTest.php index 65ce2308f..4a5a566e3 100644 --- a/Tests/File/FileTest.php +++ b/Tests/File/FileTest.php @@ -11,13 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests\File; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use Symfony\Component\HttpFoundation\File\File; -/** - * @requires extension fileinfo - */ +#[RequiresPhpExtension('fileinfo')] class FileTest extends TestCase { public function testGetMimeTypeUsesMimeTypeGuessers() @@ -103,9 +103,7 @@ public static function getFilenameFixtures() ]; } - /** - * @dataProvider getFilenameFixtures - */ + #[DataProvider('getFilenameFixtures')] public function testMoveWithNonLatinName($filename, $sanitizedFilename) { $path = __DIR__.'/Fixtures/'.$sanitizedFilename; diff --git a/Tests/File/UploadedFileTest.php b/Tests/File/UploadedFileTest.php index 9c18ad183..0cf2f5fa3 100644 --- a/Tests/File/UploadedFileTest.php +++ b/Tests/File/UploadedFileTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\File; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; @@ -168,9 +169,7 @@ public static function failedUploadedFile() } } - /** - * @dataProvider failedUploadedFile - */ + #[DataProvider('failedUploadedFile')] public function testMoveFailed(UploadedFile $file) { $exceptionClass = match ($file->getError()) { @@ -268,9 +267,7 @@ public function testIsValid() $this->assertTrue($file->isValid()); } - /** - * @dataProvider uploadedFileErrorProvider - */ + #[DataProvider('uploadedFileErrorProvider')] public function testIsInvalidOnUploadError($error) { $file = new UploadedFile( diff --git a/Tests/FileBagTest.php b/Tests/FileBagTest.php index 1afc61d2a..bea970338 100644 --- a/Tests/FileBagTest.php +++ b/Tests/FileBagTest.php @@ -209,7 +209,7 @@ protected function createTempFile() protected function setUp(): void { - mkdir(sys_get_temp_dir().'/form_test', 0777, true); + mkdir(sys_get_temp_dir().'/form_test', 0o777, true); } protected function tearDown(): void diff --git a/Tests/Fixtures/request-functional/index.php b/Tests/Fixtures/request-functional/index.php new file mode 100644 index 000000000..349a70384 --- /dev/null +++ b/Tests/Fixtures/request-functional/index.php @@ -0,0 +1,45 @@ + $request->request->all(), + 'files' => array_map( + static fn (UploadedFile $file) => [ + 'clientOriginalName' => $file->getClientOriginalName(), + 'clientMimeType' => $file->getClientMimeType(), + 'content' => $file->getContent(), + ], + $request->files->all() + ), +]); + +$r->send(); diff --git a/Tests/Fixtures/response-functional/cookie_urlencode.expected b/Tests/Fixtures/response-functional/cookie_urlencode.expected index 17a9efc66..1fbc95eef 100644 --- a/Tests/Fixtures/response-functional/cookie_urlencode.expected +++ b/Tests/Fixtures/response-functional/cookie_urlencode.expected @@ -4,8 +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: %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=/ + [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/HeaderUtilsTest.php b/Tests/HeaderUtilsTest.php index 3279b9a53..6732c8788 100644 --- a/Tests/HeaderUtilsTest.php +++ b/Tests/HeaderUtilsTest.php @@ -11,14 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\HeaderUtils; class HeaderUtilsTest extends TestCase { - /** - * @dataProvider provideHeaderToSplit - */ + #[DataProvider('provideHeaderToSplit')] public function testSplit(array $expected, string $header, string $separator) { $this->assertSame($expected, HeaderUtils::split($header, $separator)); @@ -105,9 +104,7 @@ public function testMakeDispositionInvalidDisposition() HeaderUtils::makeDisposition('invalid', 'foo.html'); } - /** - * @dataProvider provideMakeDisposition - */ + #[DataProvider('provideMakeDisposition')] public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) { $this->assertEquals($expected, HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback)); @@ -125,9 +122,7 @@ public static function provideMakeDisposition() ]; } - /** - * @dataProvider provideMakeDispositionFail - */ + #[DataProvider('provideMakeDispositionFail')] public function testMakeDispositionFail($disposition, $filename) { $this->expectException(\InvalidArgumentException::class); @@ -146,9 +141,7 @@ public static function provideMakeDispositionFail() ]; } - /** - * @dataProvider provideParseQuery - */ + #[DataProvider('provideParseQuery')] public function testParseQuery(string $query, ?string $expected = null) { $this->assertSame($expected ?? $query, http_build_query(HeaderUtils::parseQuery($query), '', '&')); diff --git a/Tests/IpUtilsTest.php b/Tests/IpUtilsTest.php index 5ed3e7b22..b6261db65 100644 --- a/Tests/IpUtilsTest.php +++ b/Tests/IpUtilsTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\IpUtils; @@ -31,9 +33,7 @@ public function testSeparateCachesPerProtocol() $this->assertTrue(IpUtils::checkIp6($ip, $subnet)); } - /** - * @dataProvider getIpv4Data - */ + #[DataProvider('getIpv4Data')] public function testIpv4($matches, $remoteAddr, $cidr) { $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); @@ -58,9 +58,7 @@ public static function getIpv4Data() ]; } - /** - * @dataProvider getIpv6Data - */ + #[DataProvider('getIpv6Data')] public function testIpv6($matches, $remoteAddr, $cidr) { if (!\defined('AF_INET6')) { @@ -94,9 +92,7 @@ public static function getIpv6Data() ]; } - /** - * @requires extension sockets - */ + #[RequiresPhpExtension('sockets')] public function testAnIpv6WithOptionDisabledIpv6() { $this->expectException(\RuntimeException::class); @@ -107,9 +103,7 @@ public function testAnIpv6WithOptionDisabledIpv6() IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); } - /** - * @dataProvider invalidIpAddressData - */ + #[DataProvider('invalidIpAddressData')] public function testInvalidIpAddressesDoNotMatch($requestIp, $proxyIp) { $this->assertFalse(IpUtils::checkIp4($requestIp, $proxyIp)); @@ -124,9 +118,7 @@ public static function invalidIpAddressData() ]; } - /** - * @dataProvider anonymizedIpData - */ + #[DataProvider('anonymizedIpData')] public function testAnonymize($ip, $expected) { $this->assertSame($expected, IpUtils::anonymize($ip)); @@ -151,9 +143,7 @@ public static function anonymizedIpData() ]; } - /** - * @dataProvider anonymizedIpDataWithBytes - */ + #[DataProvider('anonymizedIpDataWithBytes')] public function testAnonymizeWithBytes($ip, $expected, $bytesForV4, $bytesForV6) { $this->assertSame($expected, IpUtils::anonymize($ip, $bytesForV4, $bytesForV6)); @@ -215,9 +205,7 @@ public function testAnonymizeV6WithTooManyBytes() IpUtils::anonymize('anything', 1, 17); } - /** - * @dataProvider getIp4SubnetMaskZeroData - */ + #[DataProvider('getIp4SubnetMaskZeroData')] public function testIp4SubnetMaskZero($matches, $remoteAddr, $cidr) { $this->assertSame($matches, IpUtils::checkIp4($remoteAddr, $cidr)); @@ -232,9 +220,7 @@ public static function getIp4SubnetMaskZeroData() ]; } - /** - * @dataProvider getIsPrivateIpData - */ + #[DataProvider('getIsPrivateIpData')] public function testIsPrivateIp(string $ip, bool $matches) { $this->assertSame($matches, IpUtils::isPrivateIp($ip)); diff --git a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php index 087d7aeae..4316e40ca 100644 --- a/Tests/RateLimiter/AbstractRequestRateLimiterTest.php +++ b/Tests/RateLimiter/AbstractRequestRateLimiterTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\RateLimiter; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\RateLimiter\LimiterInterface; @@ -18,9 +19,7 @@ class AbstractRequestRateLimiterTest extends TestCase { - /** - * @dataProvider provideRateLimits - */ + #[DataProvider('provideRateLimits')] public function testConsume(array $rateLimits, ?RateLimit $expected) { $rateLimiter = new MockAbstractRequestRateLimiter(array_map(function (RateLimit $rateLimit) { diff --git a/Tests/RequestFunctionalTest.php b/Tests/RequestFunctionalTest.php new file mode 100644 index 000000000..2f29e89fc --- /dev/null +++ b/Tests/RequestFunctionalTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class RequestFunctionalTest extends TestCase +{ + /** @var resource|false */ + private static $server; + + public static function setUpBeforeClass(): void + { + $spec = [ + 1 => ['file', '/dev/null', 'w'], + 2 => ['file', '/dev/null', 'w'], + ]; + if (!self::$server = @proc_open('exec '.\PHP_BINARY.' -S localhost:8054', $spec, $pipes, __DIR__.'/Fixtures/request-functional')) { + self::markTestSkipped('PHP server unable to start.'); + } + sleep(1); + } + + public static function tearDownAfterClass(): void + { + if (self::$server) { + proc_terminate(self::$server); + proc_close(self::$server); + } + } + + public static function provideMethodsRequiringExplicitBodyParsing() + { + return [ + ['PUT'], + ['DELETE'], + ['PATCH'], + // PHP’s built-in server doesn’t support QUERY + ]; + } + + #[DataProvider('provideMethodsRequiringExplicitBodyParsing')] + public function testFormUrlEncodedBodyParsing(string $method) + { + $response = file_get_contents('http://localhost:8054/', false, stream_context_create([ + 'http' => [ + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'method' => $method, + 'content' => http_build_query(['foo' => 'bar']), + ], + ])); + + $this->assertSame(['foo' => 'bar'], json_decode($response, true)['request']); + } + + #[DataProvider('provideMethodsRequiringExplicitBodyParsing')] + public function testMultipartFormDataBodyParsing(string $method) + { + $response = file_get_contents('http://localhost:8054/', false, stream_context_create([ + 'http' => [ + 'header' => 'Content-Type: multipart/form-data; boundary=boundary', + 'method' => $method, + 'content' => "--boundary\r\n". + "Content-Disposition: form-data; name=foo\r\n". + "\r\n". + "bar\r\n". + "--boundary\r\n". + "Content-Disposition: form-data; name=baz; filename=baz.txt\r\n". + "Content-Type: text/plain\r\n". + "\r\n". + "qux\r\n". + '--boundary--', + ], + ])); + + $data = json_decode($response, true); + + $this->assertSame(['foo' => 'bar'], $data['request']); + $this->assertSame(['baz' => [ + 'clientOriginalName' => 'baz.txt', + 'clientMimeType' => 'text/plain', + 'content' => 'qux', + ]], $data['files']); + } +} diff --git a/Tests/RequestMatcher/AttributesRequestMatcherTest.php b/Tests/RequestMatcher/AttributesRequestMatcherTest.php index dcb2d0b98..81e8c9aa8 100644 --- a/Tests/RequestMatcher/AttributesRequestMatcherTest.php +++ b/Tests/RequestMatcher/AttributesRequestMatcherTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; @@ -18,9 +19,7 @@ class AttributesRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test(string $key, string $regexp, bool $expected) { $matcher = new AttributesRequestMatcher([$key => $regexp]); diff --git a/Tests/RequestMatcher/ExpressionRequestMatcherTest.php b/Tests/RequestMatcher/ExpressionRequestMatcherTest.php index a0118e236..600801230 100644 --- a/Tests/RequestMatcher/ExpressionRequestMatcherTest.php +++ b/Tests/RequestMatcher/ExpressionRequestMatcherTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\HttpFoundation\Request; @@ -18,9 +19,7 @@ class ExpressionRequestMatcherTest extends TestCase { - /** - * @dataProvider provideExpressions - */ + #[DataProvider('provideExpressions')] public function testMatchesWhenParentMatchesIsTrue($expression, $expected) { $request = Request::create('/foo'); diff --git a/Tests/RequestMatcher/HeaderRequestMatcherTest.php b/Tests/RequestMatcher/HeaderRequestMatcherTest.php index 47a5c7ee8..7d33ee8b3 100644 --- a/Tests/RequestMatcher/HeaderRequestMatcherTest.php +++ b/Tests/RequestMatcher/HeaderRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\HeaderRequestMatcher; class HeaderRequestMatcherTest extends TestCase { - /** - * @dataProvider getDataForArray - */ + #[DataProvider('getDataForArray')] public function testArray(array $headers, bool $matches) { $matcher = new HeaderRequestMatcher(['x-foo', 'bar']); @@ -32,9 +31,7 @@ public function testArray(array $headers, bool $matches) $this->assertSame($matches, $matcher->matches($request)); } - /** - * @dataProvider getDataForArray - */ + #[DataProvider('getDataForArray')] public function testCommaSeparatedString(array $headers, bool $matches) { $matcher = new HeaderRequestMatcher('x-foo, bar'); @@ -47,9 +44,7 @@ public function testCommaSeparatedString(array $headers, bool $matches) $this->assertSame($matches, $matcher->matches($request)); } - /** - * @dataProvider getDataForSingleString - */ + #[DataProvider('getDataForSingleString')] public function testSingleString(array $headers, bool $matches) { $matcher = new HeaderRequestMatcher('x-foo'); diff --git a/Tests/RequestMatcher/HostRequestMatcherTest.php b/Tests/RequestMatcher/HostRequestMatcherTest.php index 903bde088..badf6878d 100644 --- a/Tests/RequestMatcher/HostRequestMatcherTest.php +++ b/Tests/RequestMatcher/HostRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; class HostRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test($pattern, $isMatch) { $matcher = new HostRequestMatcher($pattern); diff --git a/Tests/RequestMatcher/IpsRequestMatcherTest.php b/Tests/RequestMatcher/IpsRequestMatcherTest.php index 57014b50a..6a88c0419 100644 --- a/Tests/RequestMatcher/IpsRequestMatcherTest.php +++ b/Tests/RequestMatcher/IpsRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher; class IpsRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test($ips, bool $expected) { $matcher = new IpsRequestMatcher($ips); diff --git a/Tests/RequestMatcher/IsJsonRequestMatcherTest.php b/Tests/RequestMatcher/IsJsonRequestMatcherTest.php index a32172ed5..a86080590 100644 --- a/Tests/RequestMatcher/IsJsonRequestMatcherTest.php +++ b/Tests/RequestMatcher/IsJsonRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; class IsJsonRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test($json, bool $isValid) { $matcher = new IsJsonRequestMatcher(); diff --git a/Tests/RequestMatcher/MethodRequestMatcherTest.php b/Tests/RequestMatcher/MethodRequestMatcherTest.php index 19db917fe..9830a9482 100644 --- a/Tests/RequestMatcher/MethodRequestMatcherTest.php +++ b/Tests/RequestMatcher/MethodRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; class MethodRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test(string $requestMethod, array|string $matcherMethod, bool $isMatch) { $matcher = new MethodRequestMatcher($matcherMethod); diff --git a/Tests/RequestMatcher/PathRequestMatcherTest.php b/Tests/RequestMatcher/PathRequestMatcherTest.php index 04ecdc913..4b4b4d15e 100644 --- a/Tests/RequestMatcher/PathRequestMatcherTest.php +++ b/Tests/RequestMatcher/PathRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; class PathRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test(string $regexp, bool $expected) { $matcher = new PathRequestMatcher($regexp); diff --git a/Tests/RequestMatcher/PortRequestMatcherTest.php b/Tests/RequestMatcher/PortRequestMatcherTest.php index 77b394f8a..53331466a 100644 --- a/Tests/RequestMatcher/PortRequestMatcherTest.php +++ b/Tests/RequestMatcher/PortRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher; class PortRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test(int $port, bool $expected) { $matcher = new PortRequestMatcher($port); diff --git a/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php b/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php index 202ca649a..d18ab6e43 100644 --- a/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php +++ b/Tests/RequestMatcher/QueryParameterRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\QueryParameterRequestMatcher; class QueryParameterRequestMatcherTest extends TestCase { - /** - * @dataProvider getDataForArray - */ + #[DataProvider('getDataForArray')] public function testArray(string $uri, bool $matches) { $matcher = new QueryParameterRequestMatcher(['foo', 'bar']); @@ -27,9 +26,7 @@ public function testArray(string $uri, bool $matches) $this->assertSame($matches, $matcher->matches($request)); } - /** - * @dataProvider getDataForArray - */ + #[DataProvider('getDataForArray')] public function testCommaSeparatedString(string $uri, bool $matches) { $matcher = new QueryParameterRequestMatcher('foo, bar'); @@ -37,9 +34,7 @@ public function testCommaSeparatedString(string $uri, bool $matches) $this->assertSame($matches, $matcher->matches($request)); } - /** - * @dataProvider getDataForSingleString - */ + #[DataProvider('getDataForSingleString')] public function testSingleString(string $uri, bool $matches) { $matcher = new QueryParameterRequestMatcher('foo'); diff --git a/Tests/RequestMatcher/SchemeRequestMatcherTest.php b/Tests/RequestMatcher/SchemeRequestMatcherTest.php index 933b3d695..f3748447b 100644 --- a/Tests/RequestMatcher/SchemeRequestMatcherTest.php +++ b/Tests/RequestMatcher/SchemeRequestMatcherTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher; class SchemeRequestMatcherTest extends TestCase { - /** - * @dataProvider getData - */ + #[DataProvider('getData')] public function test(string $requestScheme, array|string $matcherScheme, bool $isMatch) { $httpRequest = Request::create(''); diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index 01baa1d43..2d3fc8c1b 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -11,14 +11,15 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; -use Symfony\Component\HttpFoundation\InputBag; use Symfony\Component\HttpFoundation\IpUtils; -use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; @@ -29,6 +30,7 @@ protected function tearDown(): void { Request::setTrustedProxies([], -1); Request::setTrustedHosts([]); + Request::setAllowedHttpMethodOverride(null); Request::setFactory(null); \Closure::bind(static fn () => self::$formats = null, null, Request::class)(); } @@ -253,6 +255,61 @@ public function testCreate() $this->assertEquals('http://test.com/foo', $request->getUri()); } + public function testHttpMethodOverrideRespectsAllowedListWithHeader() + { + $request = Request::create('http://example.com/', 'POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'PATCH'); + + Request::setAllowedHttpMethodOverride(['PUT', 'PATCH']); + + $this->assertSame('PATCH', $request->getMethod()); + } + + public function testHttpMethodOverrideDisallowedSkipsOverrideWithHeader() + { + $request = Request::create('http://example.com/', 'POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'DELETE'); + + Request::setAllowedHttpMethodOverride(['PUT', 'PATCH']); + + $this->assertSame('POST', $request->getMethod()); + } + + public function testHttpMethodOverrideDisabledWithEmptyAllowedList() + { + $request = Request::create('http://example.com/', 'POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'PUT'); + + Request::setAllowedHttpMethodOverride([]); + + $this->assertSame('POST', $request->getMethod()); + } + + public function testHttpMethodOverrideRespectsAllowedListWithParameter() + { + Request::enableHttpMethodParameterOverride(); + Request::setAllowedHttpMethodOverride(['PUT']); + + try { + $request = Request::create('http://example.com/', 'POST', ['_method' => 'PUT']); + + $this->assertSame('PUT', $request->getMethod()); + } finally { + (new \ReflectionProperty(Request::class, 'httpMethodParameterOverride'))->setValue(null, false); + } + } + + #[TestWith(['CONNECT'])] + #[TestWith(['GET'])] + #[TestWith(['HEAD'])] + #[TestWith(['TRACE'])] + public function testHttpMethodOverrideCannotBeAppliedToCertainMethods(string $httpOverrideMethod) + { + $this->expectException(\InvalidArgumentException::class); + + Request::setAllowedHttpMethodOverride([$httpOverrideMethod]); + } + public function testCreateWithRequestUri() { $request = Request::create('http://test.com:80/foo'); @@ -303,18 +360,18 @@ public function testCreateWithRequestUri() $this->assertEquals('bar=f%5Co', $request->getQueryString()); } - /** - * @testWith ["http://foo.com\\bar"] - * ["\\\\foo.com/bar"] - * ["a\rb"] - * ["a\nb"] - * ["a\tb"] - * ["\u0000foo"] - * ["foo\u0000"] - * [" foo"] - * ["foo "] - * ["//"] - */ + #[TestWith(['http://foo.com\bar'])] + #[TestWith(['\\\foo.com/bar'])] + #[TestWith(['a +b'])] + #[TestWith(['a +b'])] + #[TestWith(['a b'])] + #[TestWith(['foo'])] + #[TestWith(['foo'])] + #[TestWith([' foo'])] + #[TestWith(['foo '])] + #[TestWith(['//'])] public function testCreateWithBadRequestUri(string $uri) { $this->expectException(BadRequestException::class); @@ -323,9 +380,7 @@ public function testCreateWithBadRequestUri(string $uri) Request::create($uri); } - /** - * @dataProvider getRequestUriData - */ + #[DataProvider('getRequestUriData')] public function testGetRequestUri($serverRequestUri, $expected, $message) { $request = new Request(); @@ -463,9 +518,7 @@ public function testGetPreferredFormat() $this->assertSame('xml', $request->getPreferredFormat()); } - /** - * @dataProvider getFormatToMimeTypeMapProvider - */ + #[DataProvider('getFormatToMimeTypeMapProvider')] public function testGetFormatFromMimeType($format, $mimeTypes) { $request = new Request(); @@ -491,18 +544,14 @@ public function testGetFormatFromMimeTypeWithParameters() $this->assertEquals('json', $request->getFormat('application/json ;charset=utf-8')); } - /** - * @dataProvider getFormatToMimeTypeMapProvider - */ + #[DataProvider('getFormatToMimeTypeMapProvider')] public function testGetMimeTypeFromFormat($format, $mimeTypes) { $request = new Request(); $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); } - /** - * @dataProvider getFormatToMimeTypeMapProvider - */ + #[DataProvider('getFormatToMimeTypeMapProvider')] public function testGetMimeTypesFromFormat($format, $mimeTypes) { $this->assertEquals($mimeTypes, Request::getMimeTypes($format)); @@ -534,6 +583,35 @@ public static function getFormatToMimeTypeMapProvider() ['rdf', ['application/rdf+xml']], ['atom', ['application/atom+xml']], ['form', ['application/x-www-form-urlencoded', 'multipart/form-data']], + ['rss', ['application/rss+xml']], + ['soap', ['application/soap+xml']], + ['html', ['text/html', 'application/xhtml+xml']], + ['problem', ['application/problem+json']], + ['hal', ['application/hal+json', 'application/hal+xml']], + ['jsonapi', ['application/vnd.api+json']], + ['yaml', ['text/yaml', 'application/x-yaml']], + ['wbxml', ['application/vnd.wap.wbxml']], + ]; + } + + #[DataProvider('getFormatWithSubtypeFallbackProvider')] + public function testGetFormatFromMimeTypeWithSubtypeFallback($expectedFormat, $mimeTypes) + { + $request = new Request(); + foreach ($mimeTypes as $mime) { + $this->assertEquals($expectedFormat, $request->getFormat($mime, true)); + } + } + + public static function getFormatWithSubtypeFallbackProvider() + { + return [ + ['cbor', ['application/example+cbor']], + ['asn1', ['application/ber-stream+ber', 'application/secure-data+der']], + ['zip', ['application/foobar+zip']], + ['tlv', ['application/device-config+tlv']], + ['pdf', ['application/pdf']], + ['csv', ['text/csv']], ]; } @@ -757,9 +835,7 @@ public function testGetUriForPath() $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); } - /** - * @dataProvider getRelativeUriForPathData - */ + #[DataProvider('getRelativeUriForPathData')] public function testGetRelativeUriForPath($expected, $pathinfo, $path) { $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path)); @@ -817,9 +893,7 @@ public function testGetSchemeAndHttpHost() $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); } - /** - * @dataProvider getQueryStringNormalizationData - */ + #[DataProvider('getQueryStringNormalizationData')] public function testGetQueryString($query, $expectedQuery, $msg) { $request = new Request(); @@ -1015,9 +1089,16 @@ public function testGetSetMethod() $this->assertSame('POST', $request->getMethod(), '->getMethod() returns the request method if invalid type is defined in query'); } - /** - * @dataProvider getClientIpsProvider - */ + public function testUnsafeMethodOverride() + { + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'get'); + + $this->assertSame('POST', $request->getMethod()); + } + + #[DataProvider('getClientIpsProvider')] public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) { $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); @@ -1025,9 +1106,7 @@ public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trus $this->assertEquals($expected[0], $request->getClientIp()); } - /** - * @dataProvider getClientIpsProvider - */ + #[DataProvider('getClientIpsProvider')] public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) { $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); @@ -1035,9 +1114,7 @@ public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $tru $this->assertEquals($expected, $request->getClientIps()); } - /** - * @dataProvider getClientIpsForwardedProvider - */ + #[DataProvider('getClientIpsForwardedProvider')] public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies) { $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); @@ -1113,9 +1190,7 @@ public static function getClientIpsProvider() ]; } - /** - * @dataProvider getClientIpsWithConflictingHeadersProvider - */ + #[DataProvider('getClientIpsWithConflictingHeadersProvider')] public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor) { $this->expectException(ConflictingHeadersException::class); @@ -1134,9 +1209,7 @@ public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXFor $request->getClientIps(); } - /** - * @dataProvider getClientIpsWithConflictingHeadersProvider - */ + #[DataProvider('getClientIpsWithConflictingHeadersProvider')] public function testGetClientIpsOnlyXHttpForwardedForTrusted($httpForwarded, $httpXForwardedFor) { $request = new Request(); @@ -1166,9 +1239,7 @@ public static function getClientIpsWithConflictingHeadersProvider() ]; } - /** - * @dataProvider getClientIpsWithAgreeingHeadersProvider - */ + #[DataProvider('getClientIpsWithAgreeingHeadersProvider')] public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps) { $request = new Request(); @@ -1245,9 +1316,7 @@ public function getContentCantBeCalledTwiceWithResourcesProvider() ]; } - /** - * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider - */ + #[DataProvider('getContentCanBeCalledTwiceWithResourcesProvider')] public function testGetContentCanBeCalledTwiceWithResources($first, $second) { $req = new Request(); @@ -1275,18 +1344,6 @@ public static function getContentCanBeCalledTwiceWithResourcesProvider() ]; } - public static function provideOverloadedMethods() - { - return [ - ['PUT'], - ['DELETE'], - ['PATCH'], - ['put'], - ['delete'], - ['patch'], - ]; - } - public function testToArrayEmpty() { $req = new Request(); @@ -1325,13 +1382,8 @@ public function testGetPayload() $this->assertSame([], $req->getPayload()->all()); } - /** - * @dataProvider provideOverloadedMethods - */ - public function testCreateFromGlobals($method) + public function testCreateFromGlobals() { - $normalizedMethod = strtoupper($method); - $_GET['foo1'] = 'bar1'; $_POST['foo2'] = 'bar2'; $_COOKIE['foo3'] = 'bar3'; @@ -1339,37 +1391,20 @@ public function testCreateFromGlobals($method) $_SERVER['foo5'] = 'bar5'; $request = Request::createFromGlobals(); - $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET'); - $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST'); - $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE'); - $this->assertEquals(['bar4'], $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES'); - $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER'); - $this->assertInstanceOf(InputBag::class, $request->request); - $this->assertInstanceOf(ParameterBag::class, $request->request); - - unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); - - $_SERVER['REQUEST_METHOD'] = $method; - $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; - $request = RequestContentProxy::createFromGlobals(); - $this->assertEquals($normalizedMethod, $request->getMethod()); - $this->assertEquals('mycontent', $request->request->get('content')); - $this->assertInstanceOf(InputBag::class, $request->request); - $this->assertInstanceOf(ParameterBag::class, $request->request); - - unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); - - Request::createFromGlobals(); + $this->assertEquals('bar1', $request->query->get('foo1'), '::createFromGlobals() uses values from $_GET'); + $this->assertEquals('bar2', $request->request->get('foo2'), '::createFromGlobals() uses values from $_POST'); + $this->assertEquals('bar3', $request->cookies->get('foo3'), '::createFromGlobals() uses values from $_COOKIE'); + $this->assertEquals(['bar4'], $request->files->get('foo4'), '::createFromGlobals() uses values from $_FILES'); + $this->assertEquals('bar5', $request->server->get('foo5'), '::createFromGlobals() uses values from $_SERVER'); + } + + public function testGetRealMethod() + { Request::enableHttpMethodParameterOverride(); - $_POST['_method'] = $method; - $_POST['foo6'] = 'bar6'; - $_SERVER['REQUEST_METHOD'] = 'PoSt'; - $request = Request::createFromGlobals(); - $this->assertEquals($normalizedMethod, $request->getMethod()); - $this->assertEquals('POST', $request->getRealMethod()); - $this->assertEquals('bar6', $request->request->get('foo6')); + $request = new Request(request: ['_method' => 'PUT'], server: ['REQUEST_METHOD' => 'PoSt']); + + $this->assertEquals('POST', $request->getRealMethod(), '->getRealMethod() returns the uppercased request method, even if it has been overridden'); - unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']); $this->disableHttpMethodParameterOverride(); } @@ -1506,34 +1541,13 @@ public function testGetPathInfo() $this->assertEquals('/', $request->getPathInfo()); } - public function testGetParameterPrecedence() - { - $request = new Request(); - $request->attributes->set('foo', 'attr'); - $request->query->set('foo', 'query'); - $request->request->set('foo', 'body'); - - $this->assertSame('attr', $request->get('foo')); - - $request->attributes->remove('foo'); - $this->assertSame('query', $request->get('foo')); - - $request->query->remove('foo'); - $this->assertSame('body', $request->get('foo')); - - $request->request->remove('foo'); - $this->assertNull($request->get('foo')); - } - public function testGetPreferredLanguage() { $request = new Request(); $this->assertNull($request->getPreferredLanguage()); } - /** - * @dataProvider providePreferredLanguage - */ + #[DataProvider('providePreferredLanguage')] public function testPreferredLanguageWithLocales(?string $expectedLocale, ?string $acceptLanguage, array $locales) { $request = new Request(); @@ -1561,7 +1575,7 @@ public static function providePreferredLanguage(): iterable yield '"fr_FR" is selected as "fr" is a similar dialect (2)' => ['fr_FR', 'ja-JP,fr;q=0.5,en_US;q=0.3', ['en_US', 'fr_FR']]; yield '"fr_FR" is selected as "fr_CA" is a similar dialect and has a greater "q" compared to "en_US" (2)' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,ru-ru;q=0.3', ['en_US', 'fr_FR']]; yield '"fr_FR" is selected as "fr_CA" is a similar dialect and has a greater "q" compared to "en"' => ['fr_FR', 'ja-JP,fr_CA;q=0.7,en;q=0.5', ['en_US', 'fr_FR']]; - yield '"fr_FR" is selected as is is an exact match as well as "en_US", but with a greater "q" parameter' => ['fr_FR', 'en-us;q=0.5,fr-fr', ['en_US', 'fr_FR']]; + yield '"fr_FR" is selected as it is an exact match as well as "en_US", but with a greater "q" parameter' => ['fr_FR', 'en-us;q=0.5,fr-fr', ['en_US', 'fr_FR']]; yield '"hi_IN" is selected as "hi_Latn_IN" is a similar dialect' => ['hi_IN', 'fr-fr,hi_Latn_IN;q=0.5', ['hi_IN', 'en_US']]; yield '"hi_Latn_IN" is selected as "hi_IN" is a similar dialect' => ['hi_Latn_IN', 'fr-fr,hi_IN;q=0.5', ['hi_Latn_IN', 'en_US']]; yield '"en_US" is selected as "en_Latn_US+variants+extensions" is a similar dialect' => ['en_US', 'en-latn-us-fonapi-u-nu-numerical-x-private,fr;q=0.5', ['fr_FR', 'en_US']]; @@ -1580,9 +1594,7 @@ public function testIsXmlHttpRequest() $this->assertFalse($request->isXmlHttpRequest()); } - /** - * @requires extension intl - */ + #[RequiresPhpExtension('intl')] public function testIntlLocale() { $request = new Request(); @@ -1644,9 +1656,7 @@ public function testGetAcceptableContentTypes() $this->assertEquals(['application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'], $request->getAcceptableContentTypes()); } - /** - * @dataProvider provideLanguages - */ + #[DataProvider('provideLanguages')] public function testGetLanguages(array $expectedLocales, ?string $acceptLanguage) { $request = new Request(); @@ -1781,9 +1791,7 @@ public function testIsMethod() $this->assertFalse($request->isMethod('post')); } - /** - * @dataProvider getBaseUrlData - */ + #[DataProvider('getBaseUrlData')] public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo) { $request = Request::create($uri, 'GET', [], [], [], $server); @@ -1918,9 +1926,7 @@ public static function getBaseUrlData() ]; } - /** - * @dataProvider baseUriDetectionOnIisWithRewriteData - */ + #[DataProvider('baseUriDetectionOnIisWithRewriteData')] public function testBaseUriDetectionOnIisWithRewrite(array $server, string $expectedBaseUrl, string $expectedPathInfo) { $request = new Request([], [], [], [], [], $server); @@ -1974,9 +1980,7 @@ public static function baseUriDetectionOnIisWithRewriteData(): \Generator ]; } - /** - * @dataProvider urlencodedStringPrefixData - */ + #[DataProvider('urlencodedStringPrefixData')] public function testUrlencodedStringPrefix($string, $prefix, $expect) { $request = new Request(); @@ -2145,9 +2149,7 @@ public function testTrustedProxiesForwarded() $this->assertTrue($request->isSecure()); } - /** - * @dataProvider iisRequestUriProvider - */ + #[DataProvider('iisRequestUriProvider')] public function testIISRequestUri($headers, $server, $expectedRequestUri) { $request = new Request(); @@ -2275,9 +2277,7 @@ public function testVeryLongHosts() $this->assertLessThan(5, microtime(true) - $start); } - /** - * @dataProvider getHostValidities - */ + #[DataProvider('getHostValidities')] public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null) { $request = Request::create('/'); @@ -2309,9 +2309,7 @@ public static function getHostValidities() ]; } - /** - * @dataProvider methodIdempotentProvider - */ + #[DataProvider('methodIdempotentProvider')] public function testMethodIdempotent($method, $idempotent) { $request = new Request(); @@ -2332,12 +2330,11 @@ public static function methodIdempotentProvider() ['OPTIONS', true], ['TRACE', true], ['CONNECT', false], + ['QUERY', true], ]; } - /** - * @dataProvider methodSafeProvider - */ + #[DataProvider('methodSafeProvider')] public function testMethodSafe($method, $safe) { $request = new Request(); @@ -2358,12 +2355,11 @@ public static function methodSafeProvider() ['OPTIONS', true], ['TRACE', true], ['CONNECT', false], + ['QUERY', true], ]; } - /** - * @dataProvider methodCacheableProvider - */ + #[DataProvider('methodCacheableProvider')] public function testMethodCacheable($method, $cacheable) { $request = new Request(); @@ -2384,12 +2380,11 @@ public static function methodCacheableProvider() ['OPTIONS', false], ['TRACE', false], ['CONNECT', false], + ['QUERY', true], ]; } - /** - * @dataProvider protocolVersionProvider - */ + #[DataProvider('protocolVersionProvider')] public function testProtocolVersion($serverProtocol, $trustedProxy, $via, $expected) { if ($trustedProxy) { @@ -2450,9 +2445,7 @@ public static function nonstandardRequestsData() ]; } - /** - * @dataProvider nonstandardRequestsData - */ + #[DataProvider('nonstandardRequestsData')] public function testNonstandardRequests($requestUri, $queryString, $expectedPathInfo, $expectedUri, $expectedBasePath = '', $expectedBaseUrl = null) { $expectedBaseUrl ??= $expectedBasePath; @@ -2583,9 +2576,7 @@ public function testTrustedPortDoesNotDefaultToZero() $this->assertSame(80, $request->getPort()); } - /** - * @dataProvider trustedProxiesRemoteAddr - */ + #[DataProvider('trustedProxiesRemoteAddr')] public function testTrustedProxiesRemoteAddr($serverRemoteAddr, $trustedProxies, $result) { $_SERVER['REMOTE_ADDR'] = $serverRemoteAddr; @@ -2603,10 +2594,8 @@ public static function trustedProxiesRemoteAddr() ]; } - /** - * @testWith ["PRIVATE_SUBNETS"] - * ["private_ranges"] - */ + #[TestWith(['PRIVATE_SUBNETS'])] + #[TestWith(['private_ranges'])] public function testTrustedProxiesPrivateSubnets(string $key) { Request::setTrustedProxies([$key], Request::HEADER_X_FORWARDED_FOR); @@ -2630,9 +2619,7 @@ public function testTrustedValuesCache() $this->assertFalse($request->isSecure()); } - /** - * @dataProvider preferSafeContentData - */ + #[DataProvider('preferSafeContentData')] public function testPreferSafeContent($server, bool $safePreferenceExpected) { $request = new Request([], [], [], [], [], $server); @@ -2695,9 +2682,7 @@ public function testReservedFlags() } } - /** - * @dataProvider provideMalformedUrls - */ + #[DataProvider('provideMalformedUrls')] public function testMalformedUrls(string $url, string $expectedException) { $this->expectException(BadRequestException::class); @@ -2731,9 +2716,7 @@ public static function provideMalformedUrls(): array ]; } - /** - * @dataProvider provideLegitimateUrls - */ + #[DataProvider('provideLegitimateUrls')] public function testLegitimateUrls(string $url) { $request = Request::create($url); @@ -2760,9 +2743,7 @@ public static function provideLegitimateUrls(): array ]; } - /** - * @dataProvider provideAcceptableContentTypesRfc9110 - */ + #[DataProvider('provideAcceptableContentTypesRfc9110')] public function testGetAcceptableContentTypesRfc9110(string $acceptHeader, array $expectedContentTypes) { $request = new Request(); @@ -2871,9 +2852,7 @@ public static function provideAcceptableContentTypesRfc9110(): iterable ]; } - /** - * @dataProvider providePreferredFormatRfc9110 - */ + #[DataProvider('providePreferredFormatRfc9110')] public function testGetPreferredFormatRfc9110(string $acceptHeader, ?string $expectedFormat, ?string $default = 'html') { $request = new Request(); @@ -3022,14 +3001,6 @@ public static function providePreferredFormatRfc9110(): iterable } } -class RequestContentProxy extends Request -{ - public function getContent($asResource = false) - { - return http_build_query(['_method' => 'PUT', 'content' => 'mycontent'], '', '&'); - } -} - class NewRequest extends Request { public function getFoo() diff --git a/Tests/ResponseFunctionalTest.php b/Tests/ResponseFunctionalTest.php index e5c6c2428..3886c97f1 100644 --- a/Tests/ResponseFunctionalTest.php +++ b/Tests/ResponseFunctionalTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; @@ -40,9 +42,7 @@ public static function tearDownAfterClass(): void } } - /** - * @dataProvider provideCookie - */ + #[DataProvider('provideCookie')] public function testCookie($fixture) { $result = file_get_contents(\sprintf('http://localhost:8054/%s.php', $fixture)); @@ -59,9 +59,7 @@ public static function provideCookie() } } - /** - * @group integration - */ + #[Group('integration')] public function testInformationalResponse() { if (!(new ExecutableFinder())->find('curl')) { diff --git a/Tests/ResponseHeaderBagTest.php b/Tests/ResponseHeaderBagTest.php index 9e61dd684..ca21fa40f 100644 --- a/Tests/ResponseHeaderBagTest.php +++ b/Tests/ResponseHeaderBagTest.php @@ -11,13 +11,12 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\ResponseHeaderBag; -/** - * @group time-sensitive - */ +#[Group('time-sensitive')] class ResponseHeaderBagTest extends TestCase { public function testAllPreserveCase() diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php index 2c761a4f8..d1e32898c 100644 --- a/Tests/ResponseTest.php +++ b/Tests/ResponseTest.php @@ -11,13 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -/** - * @group time-sensitive - */ +#[Group('time-sensitive')] class ResponseTest extends ResponseTestCase { public function testToString() @@ -63,7 +63,7 @@ public function testSend() public function testGetCharset() { $response = new Response(); - $charsetOrigin = 'UTF-8'; + $charsetOrigin = 'utf-8'; $response->setCharset($charsetOrigin); $charset = $response->getCharset(); $this->assertEquals($charsetOrigin, $charset); @@ -534,7 +534,7 @@ public function testDefaultContentType() $response = new Response('foo'); $response->prepare(new Request()); - $this->assertSame('text/html; charset=UTF-8', $response->headers->get('Content-Type')); + $this->assertSame('text/html; charset=utf-8', $response->headers->get('Content-Type')); } public function testContentTypeCharset() @@ -545,7 +545,7 @@ public function testContentTypeCharset() // force fixContentType() to be called $response->prepare(new Request()); - $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type')); + $this->assertEquals('text/css; charset=utf-8', $response->headers->get('Content-Type')); } public function testContentTypeIsNull() @@ -565,7 +565,7 @@ public function testPrepareDoesNothingIfContentTypeIsSet() $response->prepare(new Request()); - $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type')); + $this->assertEquals('text/plain; charset=utf-8', $response->headers->get('content-type')); } public function testPrepareDoesNothingIfRequestFormatIsNotDefined() @@ -574,7 +574,7 @@ public function testPrepareDoesNothingIfRequestFormatIsNotDefined() $response->prepare(new Request()); - $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); + $this->assertEquals('text/html; charset=utf-8', $response->headers->get('content-type')); } /** @@ -588,7 +588,7 @@ public function testPrepareDoesNotSetContentTypeBasedOnRequestAcceptHeader() $request->headers->set('Accept', 'application/json'); $response->prepare($request); - $this->assertSame('text/html; charset=UTF-8', $response->headers->get('content-type')); + $this->assertSame('text/html; charset=utf-8', $response->headers->get('content-type')); } public function testPrepareSetContentType() @@ -879,9 +879,7 @@ public function testIsInvalid() $this->assertFalse($response->isInvalid()); } - /** - * @dataProvider getStatusCodeFixtures - */ + #[DataProvider('getStatusCodeFixtures')] public function testSetStatusCode($code, $text, $expectedText) { $response = new Response(); @@ -897,10 +895,10 @@ public static function getStatusCodeFixtures() { return [ ['200', null, 'OK'], - ['200', false, ''], + ['200', '', ''], ['200', 'foo', 'foo'], ['199', null, 'unknown status'], - ['199', false, ''], + ['199', '', ''], ['199', 'foo', 'foo'], ]; } @@ -1005,9 +1003,7 @@ public function testSetEtag() $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null'); } - /** - * @dataProvider validContentProvider - */ + #[DataProvider('validContentProvider')] public function testSetContent($content) { $response = new Response(); @@ -1021,7 +1017,7 @@ public function testSettersAreChainable() $setters = [ 'setProtocolVersion' => '1.0', - 'setCharset' => 'UTF-8', + 'setCharset' => 'utf-8', 'setPublic' => null, 'setPrivate' => null, 'setDate' => $this->createDateTimeNow(), @@ -1128,9 +1124,7 @@ public static function ianaCodesReasonPhrasesProvider() return $ianaCodesReasonPhrases; } - /** - * @dataProvider ianaCodesReasonPhrasesProvider - */ + #[DataProvider('ianaCodesReasonPhrasesProvider')] public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase) { $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]); diff --git a/Tests/Session/Attribute/AttributeBagTest.php b/Tests/Session/Attribute/AttributeBagTest.php index afd281f0a..d20df5da3 100644 --- a/Tests/Session/Attribute/AttributeBagTest.php +++ b/Tests/Session/Attribute/AttributeBagTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; @@ -76,17 +77,13 @@ public function testGetSetName() $this->assertEquals('foo', $this->bag->getName()); } - /** - * @dataProvider attributesProvider - */ + #[DataProvider('attributesProvider')] public function testHas($key, $value, $exists) { $this->assertEquals($exists, $this->bag->has($key)); } - /** - * @dataProvider attributesProvider - */ + #[DataProvider('attributesProvider')] public function testGet($key, $value, $expected) { $this->assertEquals($value, $this->bag->get($key)); @@ -98,9 +95,7 @@ public function testGetDefaults() $this->assertEquals('default', $this->bag->get('user2.login', 'default')); } - /** - * @dataProvider attributesProvider - */ + #[DataProvider('attributesProvider')] public function testSet($key, $value, $expected) { $this->bag->set($key, $value); diff --git a/Tests/Session/SessionTest.php b/Tests/Session/SessionTest.php index 56ef60806..819ec30f9 100644 --- a/Tests/Session/SessionTest.php +++ b/Tests/Session/SessionTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; @@ -98,19 +99,15 @@ public function testGet() $this->assertEquals(1, $this->session->get('foo', 1)); } - /** - * @dataProvider setProvider - */ - public function testSet($key, $value) + #[DataProvider('setProvider')] + public function testSet($key, $value, $result) { $this->session->set($key, $value); $this->assertEquals($value, $this->session->get($key)); } - /** - * @dataProvider setProvider - */ - public function testHas($key, $value) + #[DataProvider('setProvider')] + public function testHas($key, $value, $result) { $this->session->set($key, $value); $this->assertTrue($this->session->has($key)); @@ -125,19 +122,15 @@ public function testReplace() $this->assertEquals([], $this->session->all()); } - /** - * @dataProvider setProvider - */ + #[DataProvider('setProvider')] public function testAll($key, $value, $result) { $this->session->set($key, $value); $this->assertEquals($result, $this->session->all()); } - /** - * @dataProvider setProvider - */ - public function testClear($key, $value) + #[DataProvider('setProvider')] + public function testClear($key, $value, $result) { $this->session->set('hi', 'fabien'); $this->session->set($key, $value); @@ -154,10 +147,8 @@ public static function setProvider() ]; } - /** - * @dataProvider setProvider - */ - public function testRemove($key, $value) + #[DataProvider('setProvider')] + public function testRemove($key, $value, $result) { $this->session->set('hi.world', 'have a nice day'); $this->session->set($key, $value); diff --git a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index ba489bdea..5db372430 100644 --- a/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -11,13 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Relay\Relay; use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; -/** - * @group time-sensitive - */ +#[Group('time-sensitive')] abstract class AbstractRedisSessionHandlerTestCase extends TestCase { protected const PREFIX = 'prefix_'; @@ -108,9 +108,7 @@ public function testUpdateTimestamp() $this->assertGreaterThan($lowTtl, $this->redisClient->ttl(self::PREFIX.'id')); } - /** - * @dataProvider getOptionFixtures - */ + #[DataProvider('getOptionFixtures')] public function testSupportedParam(array $options, bool $supported) { try { @@ -132,9 +130,7 @@ public static function getOptionFixtures(): array ]; } - /** - * @dataProvider getTtlFixtures - */ + #[DataProvider('getTtlFixtures')] public function testUseTtlOption(int $ttl) { $options = [ diff --git a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php index 361d3b15d..f5947b2e5 100644 --- a/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class AbstractSessionHandlerTest extends TestCase @@ -38,9 +39,7 @@ public static function tearDownAfterClass(): void } } - /** - * @dataProvider provideSession - */ + #[DataProvider('provideSession')] public function testSession($fixture) { $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]]; diff --git a/Tests/Session/Storage/Handler/IdentityMarshallerTest.php b/Tests/Session/Storage/Handler/IdentityMarshallerTest.php index 6f15f96cb..4216e9e41 100644 --- a/Tests/Session/Storage/Handler/IdentityMarshallerTest.php +++ b/Tests/Session/Storage/Handler/IdentityMarshallerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller; @@ -28,9 +29,7 @@ public function testMarshall() $this->assertSame($values, $marshaller->marshall($values, $failed)); } - /** - * @dataProvider invalidMarshallDataProvider - */ + #[DataProvider('invalidMarshallDataProvider')] public function testMarshallInvalidData($values) { $marshaller = new IdentityMarshaller(); diff --git a/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php index 3580bbd9b..c7bbb9b4f 100644 --- a/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -11,15 +11,15 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler; -/** - * @requires extension memcached - * - * @group time-sensitive - */ +#[RequiresPhpExtension('memcached')] +#[Group('time-sensitive')] class MemcachedSessionHandlerTest extends TestCase { private const PREFIX = 'prefix_'; @@ -123,9 +123,7 @@ public function testGcSession() $this->assertIsInt($this->storage->gc(123)); } - /** - * @dataProvider getOptionFixtures - */ + #[DataProvider('getOptionFixtures')] public function testSupportedOptions($options, $supported) { try { diff --git a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index fa07bd36c..2285a72e2 100644 --- a/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -20,6 +20,9 @@ use MongoDB\Driver\Exception\ConnectionException; use MongoDB\Driver\Manager; use MongoDB\Driver\Query; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; @@ -27,12 +30,10 @@ /** * @author Markus Bachmann - * - * @group integration - * @group time-sensitive - * - * @requires extension mongodb */ +#[RequiresPhpExtension('mongodb')] +#[Group('integration')] +#[Group('time-sensitive')] class MongoDbSessionHandlerTest extends TestCase { private const DABASE_NAME = 'sf-test'; @@ -44,12 +45,12 @@ class MongoDbSessionHandlerTest extends TestCase protected function setUp(): void { - $this->manager = new Manager('mongodb://'.getenv('MONGODB_HOST')); + $this->manager = new Manager(getenv('MONGODB_URI')); try { $this->manager->executeCommand(self::DABASE_NAME, new Command(['ping' => 1])); } catch (ConnectionException $e) { - $this->markTestSkipped(\sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_HOST'), $e->getMessage())); + $this->markTestSkipped(\sprintf('MongoDB Server "%s" not running: %s', getenv('MONGODB_URI'), $e->getMessage())); } $this->options = [ @@ -89,7 +90,7 @@ protected function tearDown(): void } } - /** @dataProvider provideInvalidOptions */ + #[DataProvider('provideInvalidOptions')] public function testConstructorShouldThrowExceptionForMissingOptions(array $options) { $this->expectException(\InvalidArgumentException::class); diff --git a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php index e93ed2d09..266ae9695 100644 --- a/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; @@ -19,11 +22,9 @@ * Test class for NativeFileSessionHandler. * * @author Drak - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[PreserveGlobalState(false)] +#[RunTestsInSeparateProcesses] class NativeFileSessionHandlerTest extends TestCase { public function testConstruct() @@ -36,9 +37,7 @@ public function testConstruct() $this->assertEquals('TESTING', \ini_get('session.name')); } - /** - * @dataProvider savePathDataProvider - */ + #[DataProvider('savePathDataProvider')] public function testConstructSavePath($savePath, $expectedSavePath, $path) { new NativeFileSessionHandler($savePath); diff --git a/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/Tests/Session/Storage/Handler/NullSessionHandlerTest.php index 27704b909..a9254f123 100644 --- a/Tests/Session/Storage/Handler/NullSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; @@ -20,11 +22,9 @@ * Test class for NullSessionHandler. * * @author Drak - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[PreserveGlobalState(false)] +#[RunTestsInSeparateProcesses] class NullSessionHandlerTest extends TestCase { public function testSaveHandlers() diff --git a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index 0ee76ae0b..70bbf6d2e 100644 --- a/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -12,14 +12,15 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; use Doctrine\DBAL\Schema\Schema; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; -/** - * @requires extension pdo_sqlite - * - * @group time-sensitive - */ +#[RequiresPhpExtension('pdo_sqlite')] +#[Group('time-sensitive')] class PdoSessionHandlerTest extends TestCase { private ?string $dbFile = null; @@ -259,9 +260,7 @@ public function testSessionDestroy() $this->assertSame('', $data, 'Destroyed session returns empty string'); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionGC() { $previousLifeTime = ini_set('session.gc_maxlifetime', 1000); @@ -309,9 +308,7 @@ public function testGetConnectionConnectsIfNeeded() $this->assertInstanceOf(\PDO::class, $method->invoke($storage)); } - /** - * @dataProvider provideUrlDsnPairs - */ + #[DataProvider('provideUrlDsnPairs')] public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null) { $storage = new PdoSessionHandler($url); diff --git a/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php b/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php index 492487766..08836788e 100644 --- a/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php @@ -11,11 +11,10 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\Group; use Predis\Client; -/** - * @group integration - */ +#[Group('integration')] class PredisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCase { protected function createRedisClient(string $host): Client diff --git a/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php b/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php index 0dc194ba1..71fc03cf4 100644 --- a/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php @@ -11,11 +11,10 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\Group; use Predis\Client; -/** - * @group integration - */ +#[Group('integration')] class PredisSessionHandlerTest extends AbstractRedisSessionHandlerTestCase { protected function createRedisClient(string $host): Client diff --git a/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php index af6691589..503826a19 100644 --- a/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; -/** - * @group integration - */ +use PHPUnit\Framework\Attributes\Group; + +#[Group('integration')] class RedisArraySessionHandlerTest extends AbstractRedisSessionHandlerTestCase { protected function createRedisClient(string $host): \RedisArray diff --git a/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php index 6a30f558f..ecd2340cc 100644 --- a/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; -/** - * @group integration - */ +use PHPUnit\Framework\Attributes\Group; + +#[Group('integration')] class RedisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCase { public static function setUpBeforeClass(): void diff --git a/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php b/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php index 0bb3c1620..6565e23f4 100644 --- a/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; -/** - * @group integration - */ +use PHPUnit\Framework\Attributes\Group; + +#[Group('integration')] class RedisSessionHandlerTest extends AbstractRedisSessionHandlerTestCase { protected function createRedisClient(string $host): \Redis diff --git a/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php b/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php index 76553f96d..858b70e4a 100644 --- a/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php +++ b/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php @@ -11,14 +11,13 @@ namespace Session\Storage\Handler; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Relay\Relay; use Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler\AbstractRedisSessionHandlerTestCase; -/** - * @requires extension relay - * - * @group integration - */ +#[RequiresPhpExtension('relay')] +#[Group('integration')] class RelaySessionHandlerTest extends AbstractRedisSessionHandlerTestCase { protected function createRedisClient(string $host): Relay diff --git a/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php b/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php index 41699cf56..a88ef872b 100644 --- a/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php +++ b/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php @@ -11,6 +11,10 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory; @@ -20,16 +24,12 @@ * Test class for SessionHandlerFactory. * * @author Simon - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[PreserveGlobalState(false)] +#[RunTestsInSeparateProcesses] class SessionHandlerFactoryTest extends TestCase { - /** - * @dataProvider provideConnectionDSN - */ + #[DataProvider('provideConnectionDSN')] public function testCreateFileHandler(string $connectionDSN, string $expectedPath, string $expectedHandlerType) { $handler = SessionHandlerFactory::createHandler($connectionDSN); @@ -48,18 +48,14 @@ public static function provideConnectionDSN(): array ]; } - /** - * @requires extension redis - */ + #[RequiresPhpExtension('redis')] public function testCreateRedisHandlerFromConnectionObject() { $handler = SessionHandlerFactory::createHandler($this->createMock(\Redis::class)); $this->assertInstanceOf(RedisSessionHandler::class, $handler); } - /** - * @requires extension redis - */ + #[RequiresPhpExtension('redis')] public function testCreateRedisHandlerFromDsn() { $handler = SessionHandlerFactory::createHandler('redis://localhost?prefix=foo&ttl=3600&ignored=bar'); diff --git a/Tests/Session/Storage/MetadataBagTest.php b/Tests/Session/Storage/MetadataBagTest.php index 3acdcfcac..b5f33c35b 100644 --- a/Tests/Session/Storage/MetadataBagTest.php +++ b/Tests/Session/Storage/MetadataBagTest.php @@ -11,14 +11,14 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; /** * Test class for MetadataBag. - * - * @group time-sensitive */ +#[Group('time-sensitive')] class MetadataBagTest extends TestCase { protected MetadataBag $bag; diff --git a/Tests/Session/Storage/NativeSessionStorageTest.php b/Tests/Session/Storage/NativeSessionStorageTest.php index 0d3371ce8..3871bebd2 100644 --- a/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/Tests/Session/Storage/NativeSessionStorageTest.php @@ -11,8 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; @@ -26,15 +27,11 @@ * @author Drak * * These tests require separate processes. - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[RunTestsInSeparateProcesses] +#[PreserveGlobalState(false)] class NativeSessionStorageTest extends TestCase { - use ExpectUserDeprecationMessageTrait; - private string $savePath; private $initialSessionSaveHandler; @@ -221,32 +218,6 @@ public function testCacheExpireOption() $this->assertSame('200', \ini_get('session.cache_expire')); } - /** - * @group legacy - * - * The test must only be removed when the "session.trans_sid_tags" option is removed from PHP or when the "trans_sid_tags" option is no longer supported by the native session storage. - */ - public function testTransSidTagsOption() - { - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.'); - - $previousErrorHandler = set_error_handler(function ($errno, $errstr) use (&$previousErrorHandler) { - if ('ini_set(): Usage of session.trans_sid_tags INI setting is deprecated' !== $errstr) { - return $previousErrorHandler ? $previousErrorHandler(...\func_get_args()) : false; - } - }); - - try { - $this->getStorage([ - 'trans_sid_tags' => 'a=href', - ]); - } finally { - restore_error_handler(); - } - - $this->assertSame('a=href', \ini_get('session.trans_sid_tags')); - } - public function testSetSaveHandler() { $initialSaveHandler = ini_set('session.save_handler', 'files'); @@ -368,28 +339,4 @@ public function testSaveHandlesNullSessionGracefully() $this->addToAssertionCount(1); } - - /** - * @group legacy - */ - public function testPassingDeprecatedOptions() - { - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_length" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "sid_bits_per_character" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "referer_check" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_only_cookies" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "use_trans_sid" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_hosts" option is deprecated and will be ignored in Symfony 8.0.'); - $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.2: NativeSessionStorage\'s "trans_sid_tags" option is deprecated and will be ignored in Symfony 8.0.'); - - $this->getStorage([ - 'sid_length' => 42, - 'sid_bits_per_character' => 6, - 'referer_check' => 'foo', - 'use_only_cookies' => 'foo', - 'use_trans_sid' => 'foo', - 'trans_sid_hosts' => 'foo', - 'trans_sid_tags' => 'foo', - ]); - } } diff --git a/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/Tests/Session/Storage/PhpBridgeSessionStorageTest.php index 5fbc38335..2b5350e8f 100644 --- a/Tests/Session/Storage/PhpBridgeSessionStorageTest.php +++ b/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; @@ -21,11 +23,9 @@ * @author Drak * * These tests require separate processes. - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[PreserveGlobalState(false)] +#[RunTestsInSeparateProcesses] class PhpBridgeSessionStorageTest extends TestCase { private string $savePath; diff --git a/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/Tests/Session/Storage/Proxy/AbstractProxyTest.php index 9551d52b4..c17d0d55c 100644 --- a/Tests/Session/Storage/Proxy/AbstractProxyTest.php +++ b/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -46,11 +48,8 @@ public function testIsWrapper() $this->assertFalse($this->proxy->isWrapper()); } - /** - * @runInSeparateProcess - * - * @preserveGlobalState disabled - */ + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] public function testIsActive() { $this->assertFalse($this->proxy->isActive()); @@ -58,11 +57,8 @@ public function testIsActive() $this->assertTrue($this->proxy->isActive()); } - /** - * @runInSeparateProcess - * - * @preserveGlobalState disabled - */ + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] public function testName() { $this->assertEquals(session_name(), $this->proxy->getName()); @@ -71,11 +67,8 @@ public function testName() $this->assertEquals(session_name(), $this->proxy->getName()); } - /** - * @runInSeparateProcess - * - * @preserveGlobalState disabled - */ + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] public function testNameException() { $this->expectException(\LogicException::class); @@ -83,11 +76,8 @@ public function testNameException() $this->proxy->setName('foo'); } - /** - * @runInSeparateProcess - * - * @preserveGlobalState disabled - */ + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] public function testId() { $this->assertEquals(session_id(), $this->proxy->getId()); @@ -96,11 +86,8 @@ public function testId() $this->assertEquals(session_id(), $this->proxy->getId()); } - /** - * @runInSeparateProcess - * - * @preserveGlobalState disabled - */ + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] public function testIdException() { $this->expectException(\LogicException::class); diff --git a/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php index d9c4974ef..6784d526e 100644 --- a/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php +++ b/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; @@ -21,11 +24,9 @@ * Tests for SessionHandlerProxy class. * * @author Drak - * - * @runTestsInSeparateProcesses - * - * @preserveGlobalState disabled */ +#[PreserveGlobalState(false)] +#[RunTestsInSeparateProcesses] class SessionHandlerProxyTest extends TestCase { private MockObject&\SessionHandlerInterface $mock; @@ -152,9 +153,7 @@ public function testUpdateTimestamp() $this->proxy->updateTimestamp('id', 'data'); } - /** - * @dataProvider provideNativeSessionStorageHandler - */ + #[DataProvider('provideNativeSessionStorageHandler')] public function testNativeSessionStorageSaveHandlerName($handler) { $this->assertSame('files', (new NativeSessionStorage([], $handler))->getSaveHandler()->getSaveHandlerName()); diff --git a/Tests/Test/Constraint/ResponseHeaderLocationSameTest.php b/Tests/Test/Constraint/ResponseHeaderLocationSameTest.php index d05a9f879..4ebef26f4 100644 --- a/Tests/Test/Constraint/ResponseHeaderLocationSameTest.php +++ b/Tests/Test/Constraint/ResponseHeaderLocationSameTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Test\Constraint; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; @@ -19,9 +20,7 @@ class ResponseHeaderLocationSameTest extends TestCase { - /** - * @dataProvider provideSuccessCases - */ + #[DataProvider('provideSuccessCases')] public function testConstraintSuccess(string $requestUrl, ?string $location, string $expectedLocation) { $request = Request::create($requestUrl); @@ -91,9 +90,7 @@ public static function provideSuccessCases(): iterable yield ['http://example.com/', 'http://another-example.com', 'http://another-example.com']; } - /** - * @dataProvider provideFailureCases - */ + #[DataProvider('provideFailureCases')] public function testConstraintFailure(string $requestUrl, ?string $location, string $expectedLocation) { $request = Request::create($requestUrl); diff --git a/Tests/UriSignerTest.php b/Tests/UriSignerTest.php index 81b35c28e..7dc9f1d09 100644 --- a/Tests/UriSignerTest.php +++ b/Tests/UriSignerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Symfony\Component\Clock\MockClock; use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException; @@ -20,9 +21,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\UriSigner; -/** - * @group time-sensitive - */ +#[Group('time-sensitive')] class UriSignerTest extends TestCase { public function testSign() diff --git a/Tests/UrlHelperTest.php b/Tests/UrlHelperTest.php index 02f6c64cf..ea0b92e40 100644 --- a/Tests/UrlHelperTest.php +++ b/Tests/UrlHelperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -20,9 +21,7 @@ class UrlHelperTest extends TestCase { - /** - * @dataProvider getGenerateAbsoluteUrlData - */ + #[DataProvider('getGenerateAbsoluteUrlData')] public function testGenerateAbsoluteUrl($expected, $path, $pathinfo) { $stack = new RequestStack(); @@ -55,9 +54,7 @@ public static function getGenerateAbsoluteUrlData() ]; } - /** - * @dataProvider getGenerateAbsoluteUrlRequestContextData - */ + #[DataProvider('getGenerateAbsoluteUrlRequestContextData')] public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) { if (!class_exists(RequestContext::class)) { @@ -71,9 +68,7 @@ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); } - /** - * @dataProvider getGenerateAbsoluteUrlRequestContextData - */ + #[DataProvider('getGenerateAbsoluteUrlRequestContextData')] public function testGenerateAbsoluteUrlWithRequestContextAwareInterface($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) { if (!class_exists(RequestContext::class)) { @@ -103,10 +98,8 @@ public function getContext(): RequestContext $this->assertEquals($expected, $helper->getAbsoluteUrl($path)); } - /** - * @dataProvider getGenerateAbsoluteUrlRequestContextData - */ - public function testGenerateAbsoluteUrlWithoutRequestAndRequestContext($path) + #[DataProvider('getGenerateAbsoluteUrlRequestContextData')] + public function testGenerateAbsoluteUrlWithoutRequestAndRequestContext($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected) { if (!class_exists(RequestContext::class)) { $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.'); @@ -146,9 +139,7 @@ public function testGenerateAbsoluteUrlWithScriptFileName() ); } - /** - * @dataProvider getGenerateRelativePathData - */ + #[DataProvider('getGenerateRelativePathData')] public function testGenerateRelativePath($expected, $path, $pathinfo) { $stack = new RequestStack(); diff --git a/UriSigner.php b/UriSigner.php index bb870e43c..d441e47f1 100644 --- a/UriSigner.php +++ b/UriSigner.php @@ -57,18 +57,8 @@ public function __construct( * * The expiration is added as a query string parameter. */ - public function sign(string $uri/* , \DateTimeInterface|\DateInterval|int|null $expiration = null */): string + public function sign(string $uri, \DateTimeInterface|\DateInterval|int|null $expiration = null): string { - $expiration = null; - - if (1 < \func_num_args()) { - $expiration = func_get_arg(1); - } - - if (null !== $expiration && !$expiration instanceof \DateTimeInterface && !$expiration instanceof \DateInterval && !\is_int($expiration)) { - throw new \TypeError(\sprintf('The second argument of "%s()" must be an instance of "%s" or "%s", an integer or null (%s given).', __METHOD__, \DateTimeInterface::class, \DateInterval::class, get_debug_type($expiration))); - } - $url = parse_url($uri); $params = []; @@ -121,19 +111,12 @@ public function verify(Request|string $uri): void $uri = self::normalize($uri); $status = $this->doVerify($uri); - if (self::STATUS_VALID === $status) { - return; - } - - if (self::STATUS_MISSING === $status) { - throw new UnsignedUriException(); - } - - if (self::STATUS_INVALID === $status) { - throw new UnverifiedSignedUriException(); - } - - throw new ExpiredSignedUriException(); + match ($status) { + self::STATUS_VALID => null, + self::STATUS_INVALID => throw new UnverifiedSignedUriException(), + self::STATUS_EXPIRED => throw new ExpiredSignedUriException(), + default => throw new UnsignedUriException(), + }; } private function computeHash(string $uri): string diff --git a/composer.json b/composer.json index a86b21b7c..6c54d437a 100644 --- a/composer.json +++ b/composer.json @@ -16,25 +16,22 @@ } ], "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.1" }, "require-dev": { - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5", - "symfony/clock": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + "doctrine/dbal": "<4.3" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 66c8c1836..ee5a65439 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,16 +1,17 @@ - + @@ -19,7 +20,7 @@ - + ./ @@ -28,5 +29,9 @@ ./Tests ./vendor - + + + + +