diff --git a/Attribute/AsController.php b/Attribute/AsController.php index 0f2c91d45b..f0d10a8b33 100644 --- a/Attribute/AsController.php +++ b/Attribute/AsController.php @@ -21,7 +21,4 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_FUNCTION)] class AsController { - public function __construct() - { - } } diff --git a/Attribute/AsTargetedValueResolver.php b/Attribute/AsTargetedValueResolver.php index c58f0e6dd5..0635566174 100644 --- a/Attribute/AsTargetedValueResolver.php +++ b/Attribute/AsTargetedValueResolver.php @@ -17,8 +17,10 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsTargetedValueResolver { - public function __construct( - public readonly ?string $name = null, - ) { + /** + * @param string|null $name The name with which the resolver can be targeted + */ + public function __construct(public readonly ?string $name = null) + { } } diff --git a/Attribute/Cache.php b/Attribute/Cache.php index 19d13e9228..fa2401a78c 100644 --- a/Attribute/Cache.php +++ b/Attribute/Cache.php @@ -102,6 +102,18 @@ public function __construct( * It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...). */ public int|string|null $staleIfError = null, + + /** + * Add the "no-store" Cache-Control directive when set to true. + * + * This directive indicates that no part of the response can be cached + * in any cache (not in a shared cache, nor in a private cache). + * + * Supersedes the "$public" and "$smaxage" values. + * + * @see https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.2.3 + */ + public ?bool $noStore = null, ) { } } diff --git a/Attribute/MapDateTime.php b/Attribute/MapDateTime.php index bfe48a8090..db6b4d613e 100644 --- a/Attribute/MapDateTime.php +++ b/Attribute/MapDateTime.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Attribute; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; /** * Controller parameter tag to configure DateTime arguments. @@ -19,6 +20,11 @@ #[\Attribute(\Attribute::TARGET_PARAMETER)] class MapDateTime extends ValueResolver { + /** + * @param string|null $format The DateTime format to use, @see https://php.net/datetime.format + * @param bool $disabled Whether this value resolver is disabled; this allows to enable a value resolver globally while disabling it in specific cases + * @param class-string|string $resolver The name of the resolver to use + */ public function __construct( public readonly ?string $format = null, bool $disabled = false, diff --git a/Attribute/MapQueryParameter.php b/Attribute/MapQueryParameter.php index bbc1fff273..486813a820 100644 --- a/Attribute/MapQueryParameter.php +++ b/Attribute/MapQueryParameter.php @@ -11,12 +11,15 @@ namespace Symfony\Component\HttpKernel\Attribute; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; /** * Can be used to pass a query parameter to a controller argument. * * @author Ruud Kamphuis + * @author Ionut Enache */ #[\Attribute(\Attribute::TARGET_PARAMETER)] final class MapQueryParameter extends ValueResolver @@ -24,7 +27,11 @@ final class MapQueryParameter extends ValueResolver /** * @see https://php.net/manual/filter.constants for filter, flags and options * - * @param string|null $name The name of the query parameter. If null, the name of the argument in the controller will be used. + * @param string|null $name The name of the query parameter; if null, the name of the argument in the controller will be used + * @param (FILTER_VALIDATE_*)|(FILTER_SANITIZE_*)|null $filter The filter to pass to "filter_var()", deduced from the type-hint if null + * @param int-mask-of<(FILTER_FLAG_*)|FILTER_NULL_ON_FAILURE> $flags + * @param array{min_range?: int|float, max_range?: int|float, regexp?: string, ...} $options + * @param class-string|string $resolver The name of the resolver to use */ public function __construct( public ?string $name = null, @@ -32,6 +39,7 @@ public function __construct( public int $flags = 0, public array $options = [], string $resolver = QueryParameterValueResolver::class, + public int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, ) { parent::__construct($resolver); } diff --git a/Attribute/MapQueryString.php b/Attribute/MapQueryString.php index 83722266ee..07418df85c 100644 --- a/Attribute/MapQueryString.php +++ b/Attribute/MapQueryString.php @@ -26,11 +26,18 @@ class MapQueryString extends ValueResolver { public ArgumentMetadata $metadata; + /** + * @param array $serializationContext The serialization context to use when deserializing the query string + * @param string|GroupSequence|array|null $validationGroups The validation groups to use when validating the query string mapping + * @param class-string $resolver The class name of the resolver to use + * @param int $validationFailedStatusCode The HTTP code to return if the validation fails + */ public function __construct( public readonly array $serializationContext = [], public readonly string|GroupSequence|array|null $validationGroups = null, string $resolver = RequestPayloadValueResolver::class, public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, + public readonly ?string $key = null, ) { parent::__construct($resolver); } diff --git a/Attribute/MapRequestPayload.php b/Attribute/MapRequestPayload.php index cbac606e83..cf086380c0 100644 --- a/Attribute/MapRequestPayload.php +++ b/Attribute/MapRequestPayload.php @@ -26,12 +26,21 @@ class MapRequestPayload extends ValueResolver { public ArgumentMetadata $metadata; + /** + * @param array|string|null $acceptFormat The payload formats to accept (i.e. "json", "xml") + * @param array $serializationContext The serialization context to use when deserializing the payload + * @param string|GroupSequence|array|null $validationGroups The validation groups to use when validating the query string mapping + * @param class-string $resolver The class name of the resolver to use + * @param int $validationFailedStatusCode The HTTP code to return if the validation fails + * @param class-string|string|null $type The element type for array deserialization + */ public function __construct( public readonly array|string|null $acceptFormat = null, public readonly array $serializationContext = [], public readonly string|GroupSequence|array|null $validationGroups = null, string $resolver = RequestPayloadValueResolver::class, public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, + public readonly ?string $type = null, ) { parent::__construct($resolver); } diff --git a/Attribute/MapUploadedFile.php b/Attribute/MapUploadedFile.php new file mode 100644 index 0000000000..f90b511dc7 --- /dev/null +++ b/Attribute/MapUploadedFile.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Validator\Constraint; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapUploadedFile extends ValueResolver +{ + public ArgumentMetadata $metadata; + + public function __construct( + /** @var Constraint|array|null */ + public Constraint|array|null $constraints = null, + public ?string $name = null, + string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, + ) { + parent::__construct($resolver); + } +} diff --git a/Attribute/ValueResolver.php b/Attribute/ValueResolver.php index 5875a27484..e295965fca 100644 --- a/Attribute/ValueResolver.php +++ b/Attribute/ValueResolver.php @@ -13,11 +13,15 @@ use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +/** + * Defines which value resolver should be used for a given parameter. + */ #[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)] class ValueResolver { /** - * @param class-string|string $resolver + * @param class-string|string $resolver The class name of the resolver to use + * @param bool $disabled Whether this value resolver is disabled; this allows to enable a value resolver globally while disabling it in specific cases */ public function __construct( public string $resolver, diff --git a/Attribute/WithHttpStatus.php b/Attribute/WithHttpStatus.php index 718427aacc..18aa6246dd 100644 --- a/Attribute/WithHttpStatus.php +++ b/Attribute/WithHttpStatus.php @@ -12,13 +12,16 @@ namespace Symfony\Component\HttpKernel\Attribute; /** + * Defines the HTTP status code applied to an exception. + * * @author Dejan Angelov */ #[\Attribute(\Attribute::TARGET_CLASS)] class WithHttpStatus { /** - * @param array $headers + * @param int $statusCode The HTTP status code to use + * @param array $headers The HTTP headers to add to the response */ public function __construct( public readonly int $statusCode, diff --git a/Attribute/WithLogLevel.php b/Attribute/WithLogLevel.php index 10ed3b8eaa..15e697dfd5 100644 --- a/Attribute/WithLogLevel.php +++ b/Attribute/WithLogLevel.php @@ -14,13 +14,15 @@ use Psr\Log\LogLevel; /** + * Defines the log level applied to an exception. + * * @author Dejan Angelov */ #[\Attribute(\Attribute::TARGET_CLASS)] final class WithLogLevel { /** - * @param LogLevel::* $level + * @param LogLevel::* $level The level to use to log the exception */ public function __construct(public readonly string $level) { diff --git a/Bundle/AbstractBundle.php b/Bundle/AbstractBundle.php index d24a2bc788..76f314ab97 100644 --- a/Bundle/AbstractBundle.php +++ b/Bundle/AbstractBundle.php @@ -50,7 +50,7 @@ public function getContainerExtension(): ?ExtensionInterface public function getPath(): string { - if (null === $this->path) { + if (!isset($this->path)) { $reflected = new \ReflectionObject($this); // assume the modern directory structure by default $this->path = \dirname($reflected->getFileName(), 2); diff --git a/Bundle/Bundle.php b/Bundle/Bundle.php index 0efc21a5e0..3b8006d6c3 100644 --- a/Bundle/Bundle.php +++ b/Bundle/Bundle.php @@ -24,15 +24,12 @@ */ abstract class Bundle implements BundleInterface { - protected $name; - protected $extension; - protected $path; - private string $namespace; + protected string $name; + protected ExtensionInterface|false|null $extension = null; + protected string $path; + protected ?ContainerInterface $container; - /** - * @var ContainerInterface|null - */ - protected $container; + private string $namespace; /** * @return void diff --git a/Bundle/BundleExtension.php b/Bundle/BundleExtension.php index b80bc21f25..8392218a29 100644 --- a/Bundle/BundleExtension.php +++ b/Bundle/BundleExtension.php @@ -51,7 +51,7 @@ public function prepend(ContainerBuilder $container): void $this->subject->prependExtension($configurator, $container); }; - $this->executeConfiguratorCallback($container, $callback, $this->subject); + $this->executeConfiguratorCallback($container, $callback, $this->subject, true); } public function load(array $configs, ContainerBuilder $container): void diff --git a/Bundle/BundleInterface.php b/Bundle/BundleInterface.php index 400a9e0c92..36502e8962 100644 --- a/Bundle/BundleInterface.php +++ b/Bundle/BundleInterface.php @@ -67,8 +67,5 @@ public function getNamespace(): string; */ public function getPath(): string; - /** - * @return void - */ - public function setContainer(?ContainerInterface $container); + public function setContainer(?ContainerInterface $container): void; } diff --git a/CHANGELOG.md b/CHANGELOG.md index c1743b1d14..6bf1a60ebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,51 @@ CHANGELOG ========= +7.3 +--- + + * Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving + * Support `Uid` in `#[MapQueryParameter]` + * Add `ServicesResetterInterface`, implemented by `ServicesResetter` + * Allow configuring the logging channel per type of exceptions in ErrorListener + +7.2 +--- + + * Remove `@internal` flag and add `@final` to `ServicesResetter` + * Add support for `SYMFONY_DISABLE_RESOURCE_TRACKING` env var + * Add support for configuring trusted proxies/headers/hosts via env vars + +7.1 +--- + + * Add method `isKernelTerminating()` to `ExceptionEvent` that allows to check if an exception was thrown while the kernel is being terminated + * Add `HttpException::fromStatusCode()` + * Add `$validationFailedStatusCode` argument to `#[MapQueryParameter]` that allows setting a custom HTTP status code when validation fails + * Add `NearMissValueResolverException` to let value resolvers report when an argument could be under their watch but failed to be resolved + * Add `$type` argument to `#[MapRequestPayload]` that allows mapping a list of items + * The `Extension` class is marked as internal, extend the `Extension` class from the DependencyInjection component instead + * Deprecate `Extension::addAnnotatedClassesToCompile()` + * Deprecate `AddAnnotatedClassesToCachePass` + * Deprecate the `setAnnotatedClassCache()` and `getAnnotatedClassesToCompile()` methods of the `Kernel` class + * Add `#[MapUploadedFile]` attribute to fetch, validate, and inject uploaded files into controller arguments + +7.0 +--- + + * Add argument `$reflector` to `ArgumentResolverInterface::getArguments()` and `ArgumentMetadataFactoryInterface::createArgumentMetadata()` + * Remove `ArgumentValueResolverInterface`, use `ValueResolverInterface` instead + * Remove `StreamedResponseListener` + * Remove `AbstractSurrogate::$phpEscapeMap` + * Remove `HttpKernelInterface::MASTER_REQUEST` + * Remove `terminate_on_cache_hit` option from `HttpCache` + * Require explicit argument when calling `ConfigDataCollector::setKernel()`, `RouterListener::setCurrentRequest()` + * Remove `Kernel::stripComments()` + * Remove `FileLinkFormatter`, use `FileLinkFormatter` from the ErrorHandler component instead + * Remove `UriSigner`, use `UriSigner` from the HttpFoundation component instead + * Add argument `$buildDir` to `WarmableInterface` + * Add argument `$filter` to `Profiler::find()` and `FileProfilerStorage::find()` + 6.4 --- diff --git a/CacheClearer/CacheClearerInterface.php b/CacheClearer/CacheClearerInterface.php index 5ca4265624..f40ad9b562 100644 --- a/CacheClearer/CacheClearerInterface.php +++ b/CacheClearer/CacheClearerInterface.php @@ -20,8 +20,6 @@ interface CacheClearerInterface { /** * Clears any caches necessary. - * - * @return void */ - public function clear(string $cacheDir); + public function clear(string $cacheDir): void; } diff --git a/CacheClearer/ChainCacheClearer.php b/CacheClearer/ChainCacheClearer.php index 0c541f21b8..5a8205efe4 100644 --- a/CacheClearer/ChainCacheClearer.php +++ b/CacheClearer/ChainCacheClearer.php @@ -20,14 +20,12 @@ */ class ChainCacheClearer implements CacheClearerInterface { - private iterable $clearers; - /** * @param iterable $clearers */ - public function __construct(iterable $clearers = []) - { - $this->clearers = $clearers; + public function __construct( + private iterable $clearers = [], + ) { } public function clear(string $cacheDir): void diff --git a/CacheClearer/Psr6CacheClearer.php b/CacheClearer/Psr6CacheClearer.php index 87614e9417..e33b0fed1d 100644 --- a/CacheClearer/Psr6CacheClearer.php +++ b/CacheClearer/Psr6CacheClearer.php @@ -57,10 +57,7 @@ public function clearPool(string $name): bool return $this->pools[$name]->clear(); } - /** - * @return void - */ - public function clear(string $cacheDir) + public function clear(string $cacheDir): void { foreach ($this->pools as $pool) { $pool->clear(); diff --git a/CacheWarmer/CacheWarmer.php b/CacheWarmer/CacheWarmer.php index 664110a222..0139937b35 100644 --- a/CacheWarmer/CacheWarmer.php +++ b/CacheWarmer/CacheWarmer.php @@ -18,10 +18,7 @@ */ abstract class CacheWarmer implements CacheWarmerInterface { - /** - * @return void - */ - protected function writeCacheFile(string $file, $content) + protected function writeCacheFile(string $file, $content): void { $tmpFile = @tempnam(\dirname($file), basename($file)); if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { diff --git a/CacheWarmer/CacheWarmerAggregate.php b/CacheWarmer/CacheWarmerAggregate.php index 9f1c53456c..421533df61 100644 --- a/CacheWarmer/CacheWarmerAggregate.php +++ b/CacheWarmer/CacheWarmerAggregate.php @@ -22,20 +22,17 @@ */ class CacheWarmerAggregate implements CacheWarmerInterface { - private iterable $warmers; - private bool $debug; - private ?string $deprecationLogsFilepath; private bool $optionalsEnabled = false; private bool $onlyOptionalsEnabled = false; /** * @param iterable $warmers */ - public function __construct(iterable $warmers = [], bool $debug = false, ?string $deprecationLogsFilepath = null) - { - $this->warmers = $warmers; - $this->debug = $debug; - $this->deprecationLogsFilepath = $deprecationLogsFilepath; + public function __construct( + private iterable $warmers = [], + private bool $debug = false, + private ?string $deprecationLogsFilepath = null, + ) { } public function enableOptionalWarmers(): void @@ -48,17 +45,8 @@ public function enableOnlyOptionalWarmers(): void $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; } - /** - * @param string|null $buildDir - */ - public function warmUp(string $cacheDir, string|SymfonyStyle|null $buildDir = null, ?SymfonyStyle $io = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null, ?SymfonyStyle $io = null): array { - if ($buildDir instanceof SymfonyStyle) { - trigger_deprecation('symfony/http-kernel', '6.4', 'Passing a "%s" as second argument of "%s()" is deprecated, pass it as third argument instead, after the build directory.', SymfonyStyle::class, __METHOD__); - $io = $buildDir; - $buildDir = null; - } - if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { @@ -105,7 +93,7 @@ public function warmUp(string $cacheDir, string|SymfonyStyle|null $buildDir = nu } $start = microtime(true); - foreach ((array) $warmer->warmUp($cacheDir, $buildDir) as $item) { + foreach ($warmer->warmUp($cacheDir, $buildDir) as $item) { if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item)) || ($buildDir && str_starts_with($item, \dirname($buildDir)) && !is_file($item))) { throw new \LogicException(\sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item)); } diff --git a/CacheWarmer/CacheWarmerInterface.php b/CacheWarmer/CacheWarmerInterface.php index 1f1740b7e2..d1c5101869 100644 --- a/CacheWarmer/CacheWarmerInterface.php +++ b/CacheWarmer/CacheWarmerInterface.php @@ -25,8 +25,6 @@ interface CacheWarmerInterface extends WarmableInterface * * A warmer should return true if the cache can be * generated incrementally and on-demand. - * - * @return bool */ - public function isOptional(); + public function isOptional(): bool; } diff --git a/CacheWarmer/WarmableInterface.php b/CacheWarmer/WarmableInterface.php index cd051b1add..7ffe3c0dfd 100644 --- a/CacheWarmer/WarmableInterface.php +++ b/CacheWarmer/WarmableInterface.php @@ -24,7 +24,7 @@ interface WarmableInterface * @param string $cacheDir Where warm-up artifacts should be stored * @param string|null $buildDir Where read-only artifacts should go; null when called after compile-time * - * @return string[] A list of classes or files to preload on PHP 7.4+ + * @return string[] A list of classes or files to preload */ - public function warmUp(string $cacheDir /* , string $buildDir = null */); + public function warmUp(string $cacheDir, ?string $buildDir = null): array; } diff --git a/Config/FileLocator.php b/Config/FileLocator.php index fb6bb10f1f..01fc757c43 100644 --- a/Config/FileLocator.php +++ b/Config/FileLocator.php @@ -21,12 +21,9 @@ */ class FileLocator extends BaseFileLocator { - private KernelInterface $kernel; - - public function __construct(KernelInterface $kernel) - { - $this->kernel = $kernel; - + public function __construct( + private KernelInterface $kernel, + ) { parent::__construct(); } diff --git a/Controller/ArgumentResolver.php b/Controller/ArgumentResolver.php index 991e99bdc6..b09a92f02d 100644 --- a/Controller/ArgumentResolver.php +++ b/Controller/ArgumentResolver.php @@ -18,10 +18,10 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; -use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; use Symfony\Component\HttpKernel\Exception\ResolverNotFoundException; use Symfony\Contracts\Service\ServiceProviderInterface; @@ -34,16 +34,17 @@ final class ArgumentResolver implements ArgumentResolverInterface { private ArgumentMetadataFactoryInterface $argumentMetadataFactory; private iterable $argumentValueResolvers; - private ?ContainerInterface $namedResolvers; /** - * @param iterable $argumentValueResolvers + * @param iterable $argumentValueResolvers */ - public function __construct(?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ?ContainerInterface $namedResolvers = null) - { + public function __construct( + ?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, + iterable $argumentValueResolvers = [], + private ?ContainerInterface $namedResolvers = null, + ) { $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); - $this->namedResolvers = $namedResolvers; } public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array @@ -60,7 +61,7 @@ public function getArguments(Request $request, callable $controller, ?\Reflectio if ($attribute->disabled) { $disabledResolvers[$attribute->resolver] = true; } elseif ($resolverName) { - throw new \LogicException(\sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $this->getPrettyName($controller))); + throw new \LogicException(\sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $metadata->getControllerName())); } else { $resolverName = $attribute->resolver; } @@ -79,18 +80,20 @@ public function getArguments(Request $request, callable $controller, ?\Reflectio } } + $valueResolverExceptions = []; foreach ($argumentValueResolvers as $name => $resolver) { - if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) { - continue; - } if (isset($disabledResolvers[\is_int($name) ? $resolver::class : $name])) { continue; } - $count = 0; - foreach ($resolver->resolve($request, $metadata) as $argument) { - ++$count; - $arguments[] = $argument; + try { + $count = 0; + foreach ($resolver->resolve($request, $metadata) as $argument) { + ++$count; + $arguments[] = $argument; + } + } catch (NearMissValueResolverException $e) { + $valueResolverExceptions[] = $e; } if (1 < $count && !$metadata->isVariadic()) { @@ -101,20 +104,29 @@ public function getArguments(Request $request, callable $controller, ?\Reflectio // continue to the next controller argument continue 2; } + } + + $reasons = array_map(static fn (NearMissValueResolverException $e) => $e->getMessage(), $valueResolverExceptions); + if (!$reasons) { + $reasons[] = 'Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.'; + } - if (!$resolver instanceof ValueResolverInterface) { - throw new \InvalidArgumentException(\sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver))); + $reasonCounter = 1; + if (\count($reasons) > 1) { + foreach ($reasons as $i => $reason) { + $reasons[$i] = $reasonCounter.') '.$reason; + ++$reasonCounter; } } - throw new \RuntimeException(\sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.', $this->getPrettyName($controller), $metadata->getName())); + throw new \RuntimeException(\sprintf('Controller "%s" requires the "$%s" argument that could not be resolved. '.($reasonCounter > 1 ? 'Possible reasons: ' : '').'%s', $metadata->getControllerName(), $metadata->getName(), implode(' ', $reasons))); } return $arguments; } /** - * @return iterable + * @return iterable */ public static function getDefaultArgumentValueResolvers(): iterable { @@ -126,21 +138,4 @@ public static function getDefaultArgumentValueResolvers(): iterable new VariadicValueResolver(), ]; } - - private function getPrettyName($controller): string - { - if (\is_array($controller)) { - if (\is_object($controller[0])) { - $controller[0] = get_debug_type($controller[0]); - } - - return $controller[0].'::'.$controller[1]; - } - - if (\is_object($controller)) { - return get_debug_type($controller); - } - - return $controller; - } } diff --git a/Controller/ArgumentResolver/BackedEnumValueResolver.php b/Controller/ArgumentResolver/BackedEnumValueResolver.php index c29725dd22..9193cee060 100644 --- a/Controller/ArgumentResolver/BackedEnumValueResolver.php +++ b/Controller/ArgumentResolver/BackedEnumValueResolver.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -22,33 +21,9 @@ * leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type. * * @author Maxime Steinhausser - * - * @final since Symfony 6.2 */ -class BackedEnumValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class BackedEnumValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { - return false; - } - - if ($argument->isVariadic()) { - // only target route path parameters, which cannot be variadic. - return false; - } - - // do not support if no value can be resolved at all - // letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used - // or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error. - return $request->attributes->has($argument->getName()); - } - public function resolve(Request $request, ArgumentMetadata $argument): iterable { if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { diff --git a/Controller/ArgumentResolver/DateTimeValueResolver.php b/Controller/ArgumentResolver/DateTimeValueResolver.php index 388f02b317..10ea8826f9 100644 --- a/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -14,7 +14,6 @@ use Psr\Clock\ClockInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapDateTime; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -25,23 +24,13 @@ * @author Benjamin Eberlei * @author Tim Goudriaan */ -final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class DateTimeValueResolver implements ValueResolverInterface { public function __construct( private readonly ?ClockInterface $clock = null, ) { } - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - return is_a($argument->getType(), \DateTimeInterface::class, true) && $request->attributes->has($argument->getName()); - } - public function resolve(Request $request, ArgumentMetadata $argument): array { if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) { diff --git a/Controller/ArgumentResolver/DefaultValueResolver.php b/Controller/ArgumentResolver/DefaultValueResolver.php index eb9769c09a..bf114f3f31 100644 --- a/Controller/ArgumentResolver/DefaultValueResolver.php +++ b/Controller/ArgumentResolver/DefaultValueResolver.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -21,18 +20,8 @@ * * @author Iltar van der Berg */ -final class DefaultValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class DefaultValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); - } - public function resolve(Request $request, ArgumentMetadata $argument): array { if ($argument->hasDefaultValue()) { diff --git a/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php index 594f1b8a6f..c5c862e667 100644 --- a/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php +++ b/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php @@ -14,7 +14,6 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -23,39 +22,11 @@ * * @author Simeon Kolev */ -final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class NotTaggedControllerValueResolver implements ValueResolverInterface { - private ContainerInterface $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - $controller = $request->attributes->get('_controller'); - - if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { - $controller = $controller[0].'::'.$controller[1]; - } elseif (!\is_string($controller) || '' === $controller) { - return false; - } - - if ('\\' === $controller[0]) { - $controller = ltrim($controller, '\\'); - } - - if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { - $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); - } - - return false === $this->container->has($controller); + public function __construct( + private ContainerInterface $container, + ) { } public function resolve(Request $request, ArgumentMetadata $argument): array diff --git a/Controller/ArgumentResolver/QueryParameterValueResolver.php b/Controller/ArgumentResolver/QueryParameterValueResolver.php index 0b64a444b9..5fe3d75313 100644 --- a/Controller/ArgumentResolver/QueryParameterValueResolver.php +++ b/Controller/ArgumentResolver/QueryParameterValueResolver.php @@ -15,7 +15,8 @@ use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Uid\AbstractUid; /** * Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters. @@ -23,6 +24,7 @@ * @author Ruud Kamphuis * @author Nicolas Grekas * @author Mateusz Anders + * @author Ionut Enache */ final class QueryParameterValueResolver implements ValueResolverInterface { @@ -33,12 +35,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): array } $name = $attribute->name ?? $argument->getName(); + $validationFailedCode = $attribute->validationFailedStatusCode; + if (!$request->query->has($name)) { if ($argument->isNullable() || $argument->hasDefaultValue()) { return []; } - throw new NotFoundHttpException(\sprintf('Missing query parameter "%s".', $name)); + throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Missing query parameter "%s".', $name)); } $value = $request->query->all()[$name]; @@ -52,7 +56,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $filtered = array_values(array_filter((array) $value, \is_array(...))); if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { - throw new NotFoundHttpException(\sprintf('Invalid query parameter "%s".', $name)); + throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name)); } return $filtered; @@ -70,17 +74,24 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $options['flags'] |= \FILTER_REQUIRE_SCALAR; } + $uidType = null; + if (is_subclass_of($type, AbstractUid::class)) { + $uidType = $type; + $type = 'uid'; + } + $enumType = null; $filter = match ($type) { 'array' => \FILTER_DEFAULT, - 'string' => \FILTER_DEFAULT, + 'string' => isset($attribute->options['regexp']) ? \FILTER_VALIDATE_REGEXP : \FILTER_DEFAULT, 'int' => \FILTER_VALIDATE_INT, 'float' => \FILTER_VALIDATE_FLOAT, 'bool' => \FILTER_VALIDATE_BOOL, + 'uid' => \FILTER_DEFAULT, default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) { 'int' => \FILTER_VALIDATE_INT, 'string' => \FILTER_DEFAULT, - default => throw new \LogicException(\sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')), + default => throw new \LogicException(\sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool, uid or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')), }, }; @@ -102,8 +113,12 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value); } + if (null !== $uidType) { + $value = \is_array($value) ? array_map([$uidType, 'fromString'], $value) : $uidType::fromString($value); + } + if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { - throw new NotFoundHttpException(\sprintf('Invalid query parameter "%s".', $name)); + throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name)); } if (!\is_array($value)) { @@ -117,7 +132,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array } if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { - throw new NotFoundHttpException(\sprintf('Invalid query parameter "%s".', $name)); + throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name)); } return $argument->isVariadic() ? $filtered : [$filtered]; diff --git a/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/Controller/ArgumentResolver/RequestAttributeValueResolver.php index 370e414451..2a8d48ee30 100644 --- a/Controller/ArgumentResolver/RequestAttributeValueResolver.php +++ b/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -21,18 +20,8 @@ * * @author Iltar van der Berg */ -final class RequestAttributeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class RequestAttributeValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - return !$argument->isVariadic() && $request->attributes->has($argument->getName()); - } - public function resolve(Request $request, ArgumentMetadata $argument): array { return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : []; diff --git a/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/Controller/ArgumentResolver/RequestPayloadValueResolver.php index 996eacf06f..bac746c7ab 100644 --- a/Controller/ArgumentResolver/RequestPayloadValueResolver.php +++ b/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -12,20 +12,26 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\MapQueryString; use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\HttpKernel\Attribute\MapUploadedFile; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; use Symfony\Component\Serializer\Exception\UnsupportedFormatException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Exception\ValidationFailedException; @@ -57,6 +63,7 @@ public function __construct( private readonly SerializerInterface&DenormalizerInterface $serializer, private readonly ?ValidatorInterface $validator = null, private readonly ?TranslatorInterface $translator = null, + private string $translationDomain = 'validators', ) { } @@ -64,16 +71,27 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable { $attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? $argument->getAttributesOfType(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] + ?? $argument->getAttributesOfType(MapUploadedFile::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null; if (!$attribute) { return []; } - if ($argument->isVariadic()) { + if (!$attribute instanceof MapUploadedFile && $argument->isVariadic()) { throw new \LogicException(\sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())); } + if ($attribute instanceof MapRequestPayload) { + if ('array' === $argument->getType()) { + if (!$attribute->type) { + throw new NearMissValueResolverException(\sprintf('Please set the $type argument of the #[%s] attribute to the type of the objects in the expected array.', MapRequestPayload::class)); + } + } elseif ($attribute->type) { + throw new NearMissValueResolverException(\sprintf('Please set its type to "array" when using argument $type of #[%s].', MapRequestPayload::class)); + } + } + $attribute->metadata = $argument; return [$attribute]; @@ -85,24 +103,27 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo foreach ($arguments as $i => $argument) { if ($argument instanceof MapQueryString) { - $payloadMapper = 'mapQueryString'; + $payloadMapper = $this->mapQueryString(...); $validationFailedCode = $argument->validationFailedStatusCode; } elseif ($argument instanceof MapRequestPayload) { - $payloadMapper = 'mapRequestPayload'; + $payloadMapper = $this->mapRequestPayload(...); + $validationFailedCode = $argument->validationFailedStatusCode; + } elseif ($argument instanceof MapUploadedFile) { + $payloadMapper = $this->mapUploadedFile(...); $validationFailedCode = $argument->validationFailedStatusCode; } else { continue; } $request = $event->getRequest(); - if (!$type = $argument->metadata->getType()) { + if (!$argument->metadata->getType()) { throw new \LogicException(\sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->metadata->getName())); } if ($this->validator) { $violations = new ConstraintViolationList(); try { - $payload = $this->$payloadMapper($request, $type, $argument); + $payload = $payloadMapper($request, $argument->metadata, $argument); } catch (PartialDenormalizationException $e) { $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); foreach ($e->getErrors() as $error) { @@ -115,24 +136,28 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo if ($error->canUseMessageForUser()) { $parameters['hint'] = $error->getMessage(); } - $message = $trans($template, $parameters, 'validators'); + $message = $trans($template, $parameters, $this->translationDomain); $violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null)); } $payload = $e->getData(); } if (null !== $payload && !\count($violations)) { - $violations->addAll($this->validator->validate($payload, null, $argument->validationGroups ?? null)); + $constraints = $argument->constraints ?? null; + if (\is_array($payload) && !empty($constraints) && !$constraints instanceof Assert\All) { + $constraints = new Assert\All($constraints); + } + $violations->addAll($this->validator->validate($payload, $constraints, $argument->validationGroups ?? null)); } if (\count($violations)) { - throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations)); + throw HttpException::fromStatusCode($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations)); } } else { try { - $payload = $this->$payloadMapper($request, $type, $argument); + $payload = $payloadMapper($request, $argument->metadata, $argument); } catch (PartialDenormalizationException $e) { - throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e); + throw HttpException::fromStatusCode($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e); } } @@ -140,7 +165,7 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo $payload = match (true) { $argument->metadata->hasDefaultValue() => $argument->metadata->getDefaultValue(), $argument->metadata->isNullable() => null, - default => throw new HttpException($validationFailedCode), + default => throw HttpException::fromStatusCode($validationFailedCode), }; } @@ -157,43 +182,60 @@ public static function getSubscribedEvents(): array ]; } - private function mapQueryString(Request $request, string $type, MapQueryString $attribute): ?object + private function mapQueryString(Request $request, ArgumentMetadata $argument, MapQueryString $attribute): ?object { - if (!$data = $request->query->all()) { + if (!($data = $request->query->all($attribute->key)) && ($argument->isNullable() || $argument->hasDefaultValue())) { return null; } - return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE); + return $this->serializer->denormalize($data, $argument->getType(), 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ['filter_bool' => true]); } - private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object + private function mapRequestPayload(Request $request, ArgumentMetadata $argument, MapRequestPayload $attribute): object|array|null { if (null === $format = $request->getContentTypeFormat()) { - throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, 'Unsupported format.'); + throw new UnsupportedMediaTypeHttpException('Unsupported format.'); } if ($attribute->acceptFormat && !\in_array($format, (array) $attribute->acceptFormat, true)) { - throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, \sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format)); + throw new UnsupportedMediaTypeHttpException(\sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format)); + } + + if ('array' === $argument->getType() && null !== $attribute->type) { + $type = $attribute->type.'[]'; + } else { + $type = $argument->getType(); } if ($data = $request->request->all()) { - return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE); + return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ('form' === $format ? ['filter_bool' => true] : [])); } - if ('' === $data = $request->getContent()) { + if ('' === ($data = $request->getContent()) && ($argument->isNullable() || $argument->hasDefaultValue())) { return null; } if ('form' === $format) { - throw new HttpException(Response::HTTP_BAD_REQUEST, 'Request payload contains invalid "form" data.'); + throw new BadRequestHttpException('Request payload contains invalid "form" data.'); } try { return $this->serializer->deserialize($data, $type, $format, self::CONTEXT_DESERIALIZE + $attribute->serializationContext); } catch (UnsupportedFormatException $e) { - throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, \sprintf('Unsupported format: "%s".', $format), $e); + throw new UnsupportedMediaTypeHttpException(\sprintf('Unsupported format: "%s".', $format), $e); } catch (NotEncodableValueException $e) { - throw new HttpException(Response::HTTP_BAD_REQUEST, \sprintf('Request payload contains invalid "%s" data.', $format), $e); + throw new BadRequestHttpException(\sprintf('Request payload contains invalid "%s" data.', $format), $e); + } catch (UnexpectedPropertyException $e) { + throw new BadRequestHttpException(\sprintf('Request payload contains invalid "%s" property.', $e->property), $e); } } + + private function mapUploadedFile(Request $request, ArgumentMetadata $argument, MapUploadedFile $attribute): UploadedFile|array|null + { + if (!($files = $request->files->get($attribute->name ?? $argument->getName())) && ($argument->isNullable() || $argument->hasDefaultValue())) { + return null; + } + + return $files ?? ('array' === $argument->getType() ? [] : null); + } } diff --git a/Controller/ArgumentResolver/RequestValueResolver.php b/Controller/ArgumentResolver/RequestValueResolver.php index 6347f70196..28e41181e4 100644 --- a/Controller/ArgumentResolver/RequestValueResolver.php +++ b/Controller/ArgumentResolver/RequestValueResolver.php @@ -12,29 +12,27 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; /** * Yields the same instance as the request object passed along. * * @author Iltar van der Berg */ -final class RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class RequestValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool + public function resolve(Request $request, ArgumentMetadata $argument): array { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + if (Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class)) { + return [$request]; + } - return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); - } + if (str_ends_with($argument->getType() ?? '', '\\Request')) { + throw new NearMissValueResolverException(\sprintf('Looks like you required a Request object with the wrong class name "%s". Did you mean to use "%s" instead?', $argument->getType(), Request::class)); + } - public function resolve(Request $request, ArgumentMetadata $argument): array - { - return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class) ? [$request] : []; + return []; } } diff --git a/Controller/ArgumentResolver/ServiceValueResolver.php b/Controller/ArgumentResolver/ServiceValueResolver.php index fecf59cafe..62074ef00a 100644 --- a/Controller/ArgumentResolver/ServiceValueResolver.php +++ b/Controller/ArgumentResolver/ServiceValueResolver.php @@ -14,48 +14,20 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException; /** * Yields a service keyed by _controller and argument name. * * @author Nicolas Grekas */ -final class ServiceValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class ServiceValueResolver implements ValueResolverInterface { - private ContainerInterface $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - $controller = $request->attributes->get('_controller'); - - if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { - $controller = $controller[0].'::'.$controller[1]; - } elseif (!\is_string($controller) || '' === $controller) { - return false; - } - - if ('\\' === $controller[0]) { - $controller = ltrim($controller, '\\'); - } - - if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { - $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); - } - - return $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); + public function __construct( + private ContainerInterface $container, + ) { } public function resolve(Request $request, ArgumentMetadata $argument): array @@ -83,17 +55,16 @@ public function resolve(Request $request, ArgumentMetadata $argument): array try { return [$this->container->get($controller)->get($argument->getName())]; } catch (RuntimeException $e) { - $what = \sprintf('argument $%s of "%s()"', $argument->getName(), $controller); - $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); + $what = 'argument $'.$argument->getName(); + $message = str_replace(\sprintf('service "%s"', $argument->getName()), $what, $e->getMessage()); + $what .= \sprintf(' of "%s()"', $controller); + $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $message); if ($e->getMessage() === $message) { $message = \sprintf('Cannot resolve %s: %s', $what, $message); } - $r = new \ReflectionProperty($e, 'message'); - $r->setValue($e, $message); - - throw $e; + throw new NearMissValueResolverException($message, $e->getCode(), $e); } } } diff --git a/Controller/ArgumentResolver/SessionValueResolver.php b/Controller/ArgumentResolver/SessionValueResolver.php index c8e7575d53..30b7f1d749 100644 --- a/Controller/ArgumentResolver/SessionValueResolver.php +++ b/Controller/ArgumentResolver/SessionValueResolver.php @@ -13,7 +13,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -22,27 +21,8 @@ * * @author Iltar van der Berg */ -final class SessionValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class SessionValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - if (!$request->hasSession()) { - return false; - } - - $type = $argument->getType(); - if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { - return false; - } - - return $request->getSession() instanceof $type; - } - public function resolve(Request $request, ArgumentMetadata $argument): array { if (!$request->hasSession()) { diff --git a/Controller/ArgumentResolver/TraceableValueResolver.php b/Controller/ArgumentResolver/TraceableValueResolver.php index 0cb4703b29..41fd1d9ae9 100644 --- a/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/Controller/ArgumentResolver/TraceableValueResolver.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Stopwatch\Stopwatch; @@ -22,34 +21,12 @@ * * @author Iltar van der Berg */ -final class TraceableValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class TraceableValueResolver implements ValueResolverInterface { - private ArgumentValueResolverInterface|ValueResolverInterface $inner; - private Stopwatch $stopwatch; - - public function __construct(ArgumentValueResolverInterface|ValueResolverInterface $inner, Stopwatch $stopwatch) - { - $this->inner = $inner; - $this->stopwatch = $stopwatch; - } - - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - if ($this->inner instanceof ValueResolverInterface) { - return true; - } - - $method = $this->inner::class.'::'.__FUNCTION__; - $this->stopwatch->start($method, 'controller.argument_value_resolver'); - - $return = $this->inner->supports($request, $argument); - - $this->stopwatch->stop($method); - - return $return; + public function __construct( + private ValueResolverInterface $inner, + private Stopwatch $stopwatch, + ) { } public function resolve(Request $request, ArgumentMetadata $argument): iterable diff --git a/Controller/ArgumentResolver/UidValueResolver.php b/Controller/ArgumentResolver/UidValueResolver.php index afe702300e..4a232eb8ae 100644 --- a/Controller/ArgumentResolver/UidValueResolver.php +++ b/Controller/ArgumentResolver/UidValueResolver.php @@ -12,27 +12,13 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Uid\AbstractUid; -final class UidValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class UidValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - return !$argument->isVariadic() - && \is_string($request->attributes->get($argument->getName())) - && null !== $argument->getType() - && is_subclass_of($argument->getType(), AbstractUid::class, true); - } - public function resolve(Request $request, ArgumentMetadata $argument): array { if ($argument->isVariadic() diff --git a/Controller/ArgumentResolver/VariadicValueResolver.php b/Controller/ArgumentResolver/VariadicValueResolver.php index b4946698d1..d046129f4f 100644 --- a/Controller/ArgumentResolver/VariadicValueResolver.php +++ b/Controller/ArgumentResolver/VariadicValueResolver.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -21,18 +20,8 @@ * * @author Iltar van der Berg */ -final class VariadicValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface +final class VariadicValueResolver implements ValueResolverInterface { - /** - * @deprecated since Symfony 6.2, use resolve() instead - */ - public function supports(Request $request, ArgumentMetadata $argument): bool - { - @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); - - return $argument->isVariadic() && $request->attributes->has($argument->getName()); - } - public function resolve(Request $request, ArgumentMetadata $argument): array { if (!$argument->isVariadic() || !$request->attributes->has($argument->getName())) { diff --git a/Controller/ArgumentResolverInterface.php b/Controller/ArgumentResolverInterface.php index 33d3ce2985..2090a59928 100644 --- a/Controller/ArgumentResolverInterface.php +++ b/Controller/ArgumentResolverInterface.php @@ -24,9 +24,7 @@ interface ArgumentResolverInterface /** * Returns the arguments to pass to the controller. * - * @param \ReflectionFunctionAbstract|null $reflector - * * @throws \RuntimeException When no value could be provided for a required argument */ - public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array; + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array; } diff --git a/Controller/ArgumentValueResolverInterface.php b/Controller/ArgumentValueResolverInterface.php deleted file mode 100644 index 9c3b1a0162..0000000000 --- a/Controller/ArgumentValueResolverInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Controller; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; - -/** - * Responsible for resolving the value of an argument based on its metadata. - * - * @author Iltar van der Berg - * - * @deprecated since Symfony 6.2, implement ValueResolverInterface instead - */ -interface ArgumentValueResolverInterface -{ - /** - * Whether this resolver can resolve the value for the given ArgumentMetadata. - */ - public function supports(Request $request, ArgumentMetadata $argument): bool; - - /** - * Returns the possible value(s). - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable; -} diff --git a/Controller/ContainerControllerResolver.php b/Controller/ContainerControllerResolver.php index 5cd9edb31c..3699fc0254 100644 --- a/Controller/ContainerControllerResolver.php +++ b/Controller/ContainerControllerResolver.php @@ -23,12 +23,10 @@ */ class ContainerControllerResolver extends ControllerResolver { - protected $container; - - public function __construct(ContainerInterface $container, ?LoggerInterface $logger = null) - { - $this->container = $container; - + public function __construct( + protected ContainerInterface $container, + ?LoggerInterface $logger = null, + ) { parent::__construct($logger); } diff --git a/Controller/ControllerReference.php b/Controller/ControllerReference.php index b4fdadd21e..0ecdc29b1d 100644 --- a/Controller/ControllerReference.php +++ b/Controller/ControllerReference.php @@ -26,18 +26,19 @@ */ class ControllerReference { - public $controller; - public $attributes = []; - public $query = []; + public array $attributes = []; + public array $query = []; /** * @param string $controller The controller name * @param array $attributes An array of parameters to add to the Request attributes * @param array $query An array of parameters to add to the Request query string */ - public function __construct(string $controller, array $attributes = [], array $query = []) - { - $this->controller = $controller; + public function __construct( + public string $controller, + array $attributes = [], + array $query = [], + ) { $this->attributes = $attributes; $this->query = $query; } diff --git a/Controller/ControllerResolver.php b/Controller/ControllerResolver.php index 72ece351bf..12bcd4c2dd 100644 --- a/Controller/ControllerResolver.php +++ b/Controller/ControllerResolver.php @@ -25,13 +25,12 @@ */ class ControllerResolver implements ControllerResolverInterface { - private ?LoggerInterface $logger; private array $allowedControllerTypes = []; private array $allowedControllerAttributes = [AsController::class => AsController::class]; - public function __construct(?LoggerInterface $logger = null) - { - $this->logger = $logger; + public function __construct( + private ?LoggerInterface $logger = null, + ) { } /** @@ -240,9 +239,9 @@ private function checkController(Request $request, callable $controller): callab $r = new \ReflectionFunction($controller); $name = $r->name; - if (str_contains($name, '{closure')) { + if ($r->isAnonymous()) { $name = $class = \Closure::class; - } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + } elseif ($class = $r->getClosureCalledClass()) { $class = $class->name; $name = $class.'::'.$name; } @@ -268,12 +267,6 @@ private function checkController(Request $request, callable $controller): callab $name = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)?[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $name); } - if (-1 === $request->attributes->get('_check_controller_is_allowed')) { - trigger_deprecation('symfony/http-kernel', '6.4', 'Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class); - - return $controller; - } - throw new BadRequestException(\sprintf('Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class)); } } diff --git a/Controller/ErrorController.php b/Controller/ErrorController.php index 9dd2116866..616920e5d1 100644 --- a/Controller/ErrorController.php +++ b/Controller/ErrorController.php @@ -25,15 +25,11 @@ */ class ErrorController { - private HttpKernelInterface $kernel; - private string|object|array|null $controller; - private ErrorRendererInterface $errorRenderer; - - public function __construct(HttpKernelInterface $kernel, string|object|array|null $controller, ErrorRendererInterface $errorRenderer) - { - $this->kernel = $kernel; - $this->controller = $controller; - $this->errorRenderer = $errorRenderer; + public function __construct( + private HttpKernelInterface $kernel, + private string|object|array|null $controller, + private ErrorRendererInterface $errorRenderer, + ) { } public function __invoke(\Throwable $exception): Response diff --git a/Controller/TraceableArgumentResolver.php b/Controller/TraceableArgumentResolver.php index 27cc8fb1ae..c6dac59373 100644 --- a/Controller/TraceableArgumentResolver.php +++ b/Controller/TraceableArgumentResolver.php @@ -19,21 +19,14 @@ */ class TraceableArgumentResolver implements ArgumentResolverInterface { - private ArgumentResolverInterface $resolver; - private Stopwatch $stopwatch; - - public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) - { - $this->resolver = $resolver; - $this->stopwatch = $stopwatch; + public function __construct( + private ArgumentResolverInterface $resolver, + private Stopwatch $stopwatch, + ) { } - /** - * @param \ReflectionFunctionAbstract|null $reflector - */ - public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array + public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array { - $reflector = 2 < \func_num_args() ? func_get_arg(2) : null; $e = $this->stopwatch->start('controller.get_arguments'); try { diff --git a/Controller/TraceableControllerResolver.php b/Controller/TraceableControllerResolver.php index 60f29ab5db..f6b993c992 100644 --- a/Controller/TraceableControllerResolver.php +++ b/Controller/TraceableControllerResolver.php @@ -19,13 +19,10 @@ */ class TraceableControllerResolver implements ControllerResolverInterface { - private ControllerResolverInterface $resolver; - private Stopwatch $stopwatch; - - public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) - { - $this->resolver = $resolver; - $this->stopwatch = $stopwatch; + public function __construct( + private ControllerResolverInterface $resolver, + private Stopwatch $stopwatch, + ) { } public function getController(Request $request): callable|false diff --git a/ControllerMetadata/ArgumentMetadata.php b/ControllerMetadata/ArgumentMetadata.php index d3ca53fa63..207fabc14f 100644 --- a/ControllerMetadata/ArgumentMetadata.php +++ b/ControllerMetadata/ArgumentMetadata.php @@ -20,26 +20,20 @@ class ArgumentMetadata { public const IS_INSTANCEOF = 2; - private string $name; - private ?string $type; - private bool $isVariadic; - private bool $hasDefaultValue; - private mixed $defaultValue; - private bool $isNullable; - private array $attributes; - /** * @param object[] $attributes */ - public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, mixed $defaultValue, bool $isNullable = false, array $attributes = []) - { - $this->name = $name; - $this->type = $type; - $this->isVariadic = $isVariadic; - $this->hasDefaultValue = $hasDefaultValue; - $this->defaultValue = $defaultValue; + public function __construct( + private string $name, + private ?string $type, + private bool $isVariadic, + private bool $hasDefaultValue, + private mixed $defaultValue, + private bool $isNullable = false, + private array $attributes = [], + private string $controllerName = 'n/a', + ) { $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); - $this->attributes = $attributes; } /** @@ -142,4 +136,9 @@ public function getAttributesOfType(string $name, int $flags = 0): array return $attributes; } + + public function getControllerName(): string + { + return $this->controllerName; + } } diff --git a/ControllerMetadata/ArgumentMetadataFactory.php b/ControllerMetadata/ArgumentMetadataFactory.php index 7eafdc94b0..26b80f9dcf 100644 --- a/ControllerMetadata/ArgumentMetadataFactory.php +++ b/ControllerMetadata/ArgumentMetadataFactory.php @@ -22,6 +22,7 @@ public function createArgumentMetadata(string|object|array $controller, ?\Reflec { $arguments = []; $reflector ??= new \ReflectionFunction($controller(...)); + $controllerName = $this->getPrettyName($reflector); foreach ($reflector->getParameters() as $param) { $attributes = []; @@ -31,7 +32,7 @@ public function createArgumentMetadata(string|object|array $controller, ?\Reflec } } - $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes); + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes, $controllerName); } return $arguments; @@ -53,4 +54,19 @@ private function getType(\ReflectionParameter $parameter): ?string default => $name, }; } + + private function getPrettyName(\ReflectionFunctionAbstract $r): string + { + $name = $r->name; + + if ($r instanceof \ReflectionMethod) { + return $r->class.'::'.$name; + } + + if ($r->isAnonymous() || !$class = $r->getClosureCalledClass()) { + return $name; + } + + return $class->name.'::'.$name; + } } diff --git a/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/ControllerMetadata/ArgumentMetadataFactoryInterface.php index 954f901ef2..4f4bc07866 100644 --- a/ControllerMetadata/ArgumentMetadataFactoryInterface.php +++ b/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -19,9 +19,7 @@ interface ArgumentMetadataFactoryInterface { /** - * @param \ReflectionFunctionAbstract|null $reflector - * * @return ArgumentMetadata[] */ - public function createArgumentMetadata(string|object|array $controller/* , \ReflectionFunctionAbstract $reflector = null */): array; + public function createArgumentMetadata(string|object|array $controller, ?\ReflectionFunctionAbstract $reflector = null): array; } diff --git a/DataCollector/ConfigDataCollector.php b/DataCollector/ConfigDataCollector.php index ba8fc0c317..cc8ff3ada9 100644 --- a/DataCollector/ConfigDataCollector.php +++ b/DataCollector/ConfigDataCollector.php @@ -30,12 +30,8 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Sets the Kernel associated with this Request. */ - public function setKernel(?KernelInterface $kernel = null): void + public function setKernel(KernelInterface $kernel): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/http-kernel', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } - $this->kernel = $kernel; } @@ -44,6 +40,8 @@ public function collect(Request $request, Response $response, ?\Throwable $excep $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); + $xdebugMode = getenv('XDEBUG_MODE') ?: \ini_get('xdebug.mode'); + $this->data = [ 'token' => $response->headers->get('X-Debug-Token'), 'symfony_version' => Kernel::VERSION, @@ -59,8 +57,11 @@ public function collect(Request $request, Response $response, ?\Throwable $excep 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_timezone' => date_default_timezone_get(), 'xdebug_enabled' => \extension_loaded('xdebug'), + 'xdebug_status' => \extension_loaded('xdebug') ? ($xdebugMode && 'off' !== $xdebugMode ? 'Enabled ('.$xdebugMode.')' : 'Not enabled') : 'Not installed', 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL), + 'apcu_status' => \extension_loaded('apcu') ? (filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed', 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL), + 'zend_opcache_status' => \extension_loaded('Zend OPcache') ? (filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed', 'bundles' => [], 'sapi_name' => \PHP_SAPI, ]; @@ -196,6 +197,11 @@ public function hasXdebug(): bool return $this->data['xdebug_enabled']; } + public function getXdebugStatus(): string + { + return $this->data['xdebug_status']; + } + /** * Returns true if the function xdebug_info is available. */ @@ -212,6 +218,11 @@ public function hasApcu(): bool return $this->data['apcu_enabled']; } + public function getApcuStatus(): string + { + return $this->data['apcu_status']; + } + /** * Returns true if Zend OPcache is enabled. */ @@ -220,6 +231,11 @@ public function hasZendOpcache(): bool return $this->data['zend_opcache_enabled']; } + public function getZendOpcacheStatus(): string + { + return $this->data['zend_opcache_status']; + } + public function getBundles(): array|Data { return $this->data['bundles']; diff --git a/DataCollector/DataCollector.php b/DataCollector/DataCollector.php index fdc73de06f..3238e2bb8d 100644 --- a/DataCollector/DataCollector.php +++ b/DataCollector/DataCollector.php @@ -28,10 +28,7 @@ */ abstract class DataCollector implements DataCollectorInterface { - /** - * @var array|Data - */ - protected $data = []; + protected array|Data $data = []; private ClonerInterface $cloner; @@ -58,9 +55,9 @@ protected function cloneVar(mixed $var): Data /** * @return callable[] The casters to add to the cloner */ - protected function getCasters() + protected function getCasters(): array { - $casters = [ + return [ '*' => function ($v, array $a, Stub $s, $isNested) { if (!$v instanceof Stub) { $b = $a; @@ -85,8 +82,6 @@ protected function getCasters() return $a; }, ] + ReflectionCaster::UNSET_CLOSURE_FILE_INFO; - - return $casters; } public function __sleep(): array @@ -94,10 +89,7 @@ public function __sleep(): array return ['data']; } - /** - * @return void - */ - public function __wakeup() + public function __wakeup(): void { } diff --git a/DataCollector/DumpDataCollector.php b/DataCollector/DumpDataCollector.php index 2300acf914..f1d13d326f 100644 --- a/DataCollector/DumpDataCollector.php +++ b/DataCollector/DumpDataCollector.php @@ -31,7 +31,6 @@ */ class DumpDataCollector extends DataCollector implements DataDumperInterface { - private ?Stopwatch $stopwatch = null; private string|FileLinkFormatter|false $fileLinkFormat; private int $dataCount = 0; private bool $isCollected = true; @@ -39,19 +38,20 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface private int $clonesIndex = 0; private array $rootRefs; private string $charset; - private ?RequestStack $requestStack; - private DataDumperInterface|Connection|null $dumper; private mixed $sourceContextProvider; private bool $webMode; - public function __construct(?Stopwatch $stopwatch = null, string|FileLinkFormatter|null $fileLinkFormat = null, ?string $charset = null, ?RequestStack $requestStack = null, DataDumperInterface|Connection|null $dumper = null, ?bool $webMode = null) - { + public function __construct( + private ?Stopwatch $stopwatch = null, + string|FileLinkFormatter|null $fileLinkFormat = null, + ?string $charset = null, + private ?RequestStack $requestStack = null, + private DataDumperInterface|Connection|null $dumper = null, + ?bool $webMode = null, + ) { $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->stopwatch = $stopwatch; $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && false === $fileLinkFormat->format('', 0) ? false : $fileLinkFormat; $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'; - $this->requestStack = $requestStack; - $this->dumper = $dumper; $this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true); // All clones share these properties by reference: @@ -121,12 +121,12 @@ public function collect(Request $request, Response $response, ?\Throwable $excep ) { if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type') ?? '', 'html')) { $dumper = new HtmlDumper('php://output', $this->charset); - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + foreach ($this->data as $dump) { $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); } @@ -180,7 +180,7 @@ public function __wakeup(): void } } - self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); + self::__construct($this->stopwatch ?? null, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); } public function getDumpsCount(): int @@ -235,12 +235,12 @@ public function __destruct() if ($this->webMode) { $dumper = new HtmlDumper('php://output', $this->charset); - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); - $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + foreach ($this->data as $i => $dump) { $this->data[$i] = null; $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line'], $dump['label'] ?? ''); diff --git a/DataCollector/LoggerDataCollector.php b/DataCollector/LoggerDataCollector.php index cf17e7a739..29024f6e74 100644 --- a/DataCollector/LoggerDataCollector.php +++ b/DataCollector/LoggerDataCollector.php @@ -27,16 +27,15 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface { private ?DebugLoggerInterface $logger; - private ?string $containerPathPrefix; private ?Request $currentRequest = null; - private ?RequestStack $requestStack; private ?array $processedLogs = null; - public function __construct(?object $logger = null, ?string $containerPathPrefix = null, ?RequestStack $requestStack = null) - { + public function __construct( + ?object $logger = null, + private ?string $containerPathPrefix = null, + private ?RequestStack $requestStack = null, + ) { $this->logger = DebugLoggerConfigurator::getDebugLogger($logger); - $this->containerPathPrefix = $containerPathPrefix; - $this->requestStack = $requestStack; } public function collect(Request $request, Response $response, ?\Throwable $exception = null): void @@ -192,7 +191,7 @@ private function getContainerDeprecationLogs(): array $log['priorityName'] = 'DEBUG'; $log['channel'] = null; $log['scream'] = false; - unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); + unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['count']); $logs[] = $log; } @@ -234,10 +233,10 @@ private function sanitizeLogs(array $logs): array $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { - if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + if (isset($silencedLogs[$id = spl_object_id($exception)])) { continue; } - $silencedLogs[$h] = true; + $silencedLogs[$id] = true; if (!isset($sanitizedLogs[$message])) { $sanitizedLogs[$message] = $log + [ @@ -313,10 +312,10 @@ private function computeErrorsCount(array $containerDeprecationLogs): array if ($this->isSilencedOrDeprecationErrorLog($log)) { $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { - if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + if (isset($silencedLogs[$id = spl_object_id($exception)])) { continue; } - $silencedLogs[$h] = true; + $silencedLogs[$id] = true; $count['scream_count'] += $exception->count; } else { ++$count['deprecation_count']; diff --git a/DataCollector/RequestDataCollector.php b/DataCollector/RequestDataCollector.php index 4792f160c0..235bd4c74f 100644 --- a/DataCollector/RequestDataCollector.php +++ b/DataCollector/RequestDataCollector.php @@ -36,12 +36,11 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter */ private \SplObjectStorage $controllers; private array $sessionUsages = []; - private ?RequestStack $requestStack; - public function __construct(?RequestStack $requestStack = null) - { + public function __construct( + private ?RequestStack $requestStack = null, + ) { $this->controllers = new \SplObjectStorage(); - $this->requestStack = $requestStack; } public function collect(Request $request, Response $response, ?\Throwable $exception = null): void @@ -195,74 +194,47 @@ public function getPathInfo(): string return $this->data['path_info']; } - /** - * @return ParameterBag - */ - public function getRequestRequest() + public function getRequestRequest(): ParameterBag { return new ParameterBag($this->data['request_request']->getValue()); } - /** - * @return ParameterBag - */ - public function getRequestQuery() + public function getRequestQuery(): ParameterBag { return new ParameterBag($this->data['request_query']->getValue()); } - /** - * @return ParameterBag - */ - public function getRequestFiles() + public function getRequestFiles(): ParameterBag { return new ParameterBag($this->data['request_files']->getValue()); } - /** - * @return ParameterBag - */ - public function getRequestHeaders() + public function getRequestHeaders(): ParameterBag { return new ParameterBag($this->data['request_headers']->getValue()); } - /** - * @return ParameterBag - */ - public function getRequestServer(bool $raw = false) + public function getRequestServer(bool $raw = false): ParameterBag { return new ParameterBag($this->data['request_server']->getValue($raw)); } - /** - * @return ParameterBag - */ - public function getRequestCookies(bool $raw = false) + public function getRequestCookies(bool $raw = false): ParameterBag { return new ParameterBag($this->data['request_cookies']->getValue($raw)); } - /** - * @return ParameterBag - */ - public function getRequestAttributes() + public function getRequestAttributes(): ParameterBag { return new ParameterBag($this->data['request_attributes']->getValue()); } - /** - * @return ParameterBag - */ - public function getResponseHeaders() + public function getResponseHeaders(): ParameterBag { return new ParameterBag($this->data['response_headers']->getValue()); } - /** - * @return ParameterBag - */ - public function getResponseCookies() + public function getResponseCookies(): ParameterBag { return new ParameterBag($this->data['response_cookies']->getValue()); } @@ -300,18 +272,12 @@ public function getContent() return $this->data['content']; } - /** - * @return bool - */ - public function isJsonRequest() + public function isJsonRequest(): bool { return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); } - /** - * @return string|null - */ - public function getPrettyJson() + public function getPrettyJson(): ?string { $decoded = json_decode($this->getContent()); @@ -343,10 +309,7 @@ public function getLocale(): string return $this->data['locale']; } - /** - * @return ParameterBag - */ - public function getDotenvVars() + public function getDotenvVars(): ParameterBag { return new ParameterBag($this->data['dotenv_vars']->getValue()); } @@ -505,12 +468,12 @@ private function parseController(array|object|string|null $controller): array|st 'line' => $r->getStartLine(), ]; - if (str_contains($r->name, '{closure')) { + if ($r->isAnonymous()) { return $controller; } $controller['method'] = $r->name; - if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + if ($class = $r->getClosureCalledClass()) { $controller['class'] = $class->name; } else { return $r->name; diff --git a/DataCollector/RouterDataCollector.php b/DataCollector/RouterDataCollector.php index 4d91fd6e14..81d3533a63 100644 --- a/DataCollector/RouterDataCollector.php +++ b/DataCollector/RouterDataCollector.php @@ -24,7 +24,7 @@ class RouterDataCollector extends DataCollector /** * @var \SplObjectStorage */ - protected $controllers; + protected \SplObjectStorage $controllers; public function __construct() { @@ -40,7 +40,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep $this->data['redirect'] = true; $this->data['url'] = $response->getTargetUrl(); - if ($this->controllers->contains($request)) { + if ($this->controllers->offsetExists($request)) { $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); } } @@ -48,10 +48,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep unset($this->controllers[$request]); } - /** - * @return void - */ - public function reset() + public function reset(): void { $this->controllers = new \SplObjectStorage(); @@ -62,20 +59,15 @@ public function reset() ]; } - /** - * @return string - */ - protected function guessRoute(Request $request, string|object|array $controller) + protected function guessRoute(Request $request, string|object|array $controller): string { return 'n/a'; } /** * Remembers the controller associated to each request. - * - * @return void */ - public function onKernelController(ControllerEvent $event) + public function onKernelController(ControllerEvent $event): void { $this->controllers[$event->getRequest()] = $event->getController(); } diff --git a/DataCollector/TimeDataCollector.php b/DataCollector/TimeDataCollector.php index 9799a1333d..c9b830ff03 100644 --- a/DataCollector/TimeDataCollector.php +++ b/DataCollector/TimeDataCollector.php @@ -24,13 +24,10 @@ */ class TimeDataCollector extends DataCollector implements LateDataCollectorInterface { - private ?KernelInterface $kernel; - private ?Stopwatch $stopwatch; - - public function __construct(?KernelInterface $kernel = null, ?Stopwatch $stopwatch = null) - { - $this->kernel = $kernel; - $this->stopwatch = $stopwatch; + public function __construct( + private readonly ?KernelInterface $kernel = null, + private readonly ?Stopwatch $stopwatch = null, + ) { $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; } diff --git a/Debug/ErrorHandlerConfigurator.php b/Debug/ErrorHandlerConfigurator.php index 5b3e1cdddf..38650bebb5 100644 --- a/Debug/ErrorHandlerConfigurator.php +++ b/Debug/ErrorHandlerConfigurator.php @@ -23,12 +23,8 @@ */ class ErrorHandlerConfigurator { - private ?LoggerInterface $logger; - private ?LoggerInterface $deprecationLogger; private array|int|null $levels; private ?int $throwAt; - private bool $scream; - private bool $scope; /** * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants @@ -36,14 +32,16 @@ class ErrorHandlerConfigurator * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged * @param bool $scope Enables/disables scoping mode */ - public function __construct(?LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, ?LoggerInterface $deprecationLogger = null) - { - $this->logger = $logger; + public function __construct( + private ?LoggerInterface $logger = null, + array|int|null $levels = \E_ALL, + ?int $throwAt = \E_ALL, + private bool $scream = true, + private bool $scope = true, + private ?LoggerInterface $deprecationLogger = null, + ) { $this->levels = $levels ?? \E_ALL; $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); - $this->scream = $scream; - $this->scope = $scope; - $this->deprecationLogger = $deprecationLogger; } /** diff --git a/Debug/FileLinkFormatter.php b/Debug/FileLinkFormatter.php deleted file mode 100644 index 600a460fb6..0000000000 --- a/Debug/FileLinkFormatter.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Debug; - -use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter as ErrorHandlerFileLinkFormatter; - -trigger_deprecation('symfony/http-kernel', '6.4', 'The "%s" class is deprecated, use "%s" instead.', FileLinkFormatter::class, ErrorHandlerFileLinkFormatter::class); - -class_exists(ErrorHandlerFileLinkFormatter::class); - -if (!class_exists(FileLinkFormatter::class, false)) { - class_alias(ErrorHandlerFileLinkFormatter::class, FileLinkFormatter::class); -} - -if (false) { - /** - * @deprecated since Symfony 6.4, use FileLinkFormatter from the ErrorHandler component instead - */ - class FileLinkFormatter extends ErrorHandlerFileLinkFormatter - { - } -} diff --git a/Debug/TraceableEventDispatcher.php b/Debug/TraceableEventDispatcher.php index f3101d5b14..915862eddb 100644 --- a/Debug/TraceableEventDispatcher.php +++ b/Debug/TraceableEventDispatcher.php @@ -25,9 +25,12 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher { protected function beforeDispatch(string $eventName, object $event): void { + if ($this->disabled?->__invoke()) { + return; + } switch ($eventName) { case KernelEvents::REQUEST: - $event->getRequest()->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $event->getRequest()->attributes->set('_stopwatch_token', bin2hex(random_bytes(3))); $this->stopwatch->openSection(); break; case KernelEvents::VIEW: @@ -57,6 +60,9 @@ protected function beforeDispatch(string $eventName, object $event): void protected function afterDispatch(string $eventName, object $event): void { + if ($this->disabled?->__invoke()) { + return; + } switch ($eventName) { case KernelEvents::CONTROLLER_ARGUMENTS: $this->stopwatch->start('controller', 'section'); diff --git a/DependencyInjection/AddAnnotatedClassesToCachePass.php b/DependencyInjection/AddAnnotatedClassesToCachePass.php index 1924b1ddb0..c8ed6b8e41 100644 --- a/DependencyInjection/AddAnnotatedClassesToCachePass.php +++ b/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -17,24 +17,23 @@ use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\HttpKernel\Kernel; +trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s" class is deprecated since Symfony 7.1 and will be removed in 8.0.', AddAnnotatedClassesToCachePass::class); + /** * Sets the classes to compile in the cache for the container. * * @author Fabien Potencier + * + * @deprecated since Symfony 7.1, to be removed in 8.0 */ class AddAnnotatedClassesToCachePass implements CompilerPassInterface { - private Kernel $kernel; - - public function __construct(Kernel $kernel) - { - $this->kernel = $kernel; + public function __construct( + private Kernel $kernel, + ) { } - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $annotatedClasses = []; foreach ($container->getExtensions() as $extension) { diff --git a/DependencyInjection/ConfigurableExtension.php b/DependencyInjection/ConfigurableExtension.php index 12d468cf04..714fdb7195 100644 --- a/DependencyInjection/ConfigurableExtension.php +++ b/DependencyInjection/ConfigurableExtension.php @@ -34,8 +34,6 @@ final public function load(array $configs, ContainerBuilder $container): void /** * Configures the passed container according to the merged configuration. - * - * @return void */ - abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void; } diff --git a/DependencyInjection/ControllerArgumentValueResolverPass.php b/DependencyInjection/ControllerArgumentValueResolverPass.php index d3b157418e..d760e3bcc1 100644 --- a/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -30,10 +30,7 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('argument_resolver')) { return; diff --git a/DependencyInjection/Extension.php b/DependencyInjection/Extension.php index d72efa1724..87b81a8c5e 100644 --- a/DependencyInjection/Extension.php +++ b/DependencyInjection/Extension.php @@ -17,6 +17,8 @@ * Allow adding classes to the class cache. * * @author Fabien Potencier + * + * @internal since Symfony 7.1, to be deprecated in 8.1; use Symfony\Component\DependencyInjection\Extension\Extension instead */ abstract class Extension extends BaseExtension { @@ -24,21 +26,29 @@ abstract class Extension extends BaseExtension /** * Gets the annotated classes to cache. + * + * @return string[] + * + * @deprecated since Symfony 7.1, to be removed in 8.0 */ public function getAnnotatedClassesToCompile(): array { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + return $this->annotatedClasses; } /** * Adds annotated classes to the class cache. * - * @param array $annotatedClasses An array of class patterns + * @param string[] $annotatedClasses An array of class patterns * - * @return void + * @deprecated since Symfony 7.1, to be removed in 8.0 */ - public function addAnnotatedClassesToCompile(array $annotatedClasses) + public function addAnnotatedClassesToCompile(array $annotatedClasses): void { + trigger_deprecation('symfony/http-kernel', '7.1', 'The "%s()" method is deprecated since Symfony 7.1 and will be removed in 8.0.', __METHOD__); + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); } } diff --git a/DependencyInjection/FragmentRendererPass.php b/DependencyInjection/FragmentRendererPass.php index 9a32b8a4eb..2ac044f1a3 100644 --- a/DependencyInjection/FragmentRendererPass.php +++ b/DependencyInjection/FragmentRendererPass.php @@ -25,10 +25,7 @@ */ class FragmentRendererPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('fragment.handler')) { return; diff --git a/DependencyInjection/LazyLoadingFragmentHandler.php b/DependencyInjection/LazyLoadingFragmentHandler.php index 944b5d0bc7..d8ff64bcc2 100644 --- a/DependencyInjection/LazyLoadingFragmentHandler.php +++ b/DependencyInjection/LazyLoadingFragmentHandler.php @@ -23,17 +23,16 @@ */ class LazyLoadingFragmentHandler extends FragmentHandler { - private ContainerInterface $container; - /** * @var array */ private array $initialized = []; - public function __construct(ContainerInterface $container, RequestStack $requestStack, bool $debug = false) - { - $this->container = $container; - + public function __construct( + private ContainerInterface $container, + RequestStack $requestStack, + bool $debug = false, + ) { parent::__construct($requestStack, [], $debug); } diff --git a/DependencyInjection/LoggerPass.php b/DependencyInjection/LoggerPass.php index 0061a577c7..7566d7358c 100644 --- a/DependencyInjection/LoggerPass.php +++ b/DependencyInjection/LoggerPass.php @@ -25,10 +25,7 @@ */ class LoggerPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->has(LoggerInterface::class)) { $container->setAlias(LoggerInterface::class, 'logger'); diff --git a/DependencyInjection/MergeExtensionConfigurationPass.php b/DependencyInjection/MergeExtensionConfigurationPass.php index d65dbbab03..8336d8c297 100644 --- a/DependencyInjection/MergeExtensionConfigurationPass.php +++ b/DependencyInjection/MergeExtensionConfigurationPass.php @@ -21,14 +21,12 @@ */ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass { - private array $extensions; - /** * @param string[] $extensions */ - public function __construct(array $extensions) - { - $this->extensions = $extensions; + public function __construct( + private array $extensions, + ) { } public function process(ContainerBuilder $container): void diff --git a/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index e2f50dcba1..a5a863cc15 100644 --- a/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -34,10 +34,7 @@ */ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) { return; @@ -54,8 +51,6 @@ public function process(ContainerBuilder $container) } } - $emptyAutowireAttributes = class_exists(Autowire::class) ? null : []; - foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) { $def = $container->getDefinition($id); $def->setPublic(true); @@ -127,11 +122,12 @@ public function process(ContainerBuilder $container) // create a per-method map of argument-names to service/type-references $args = []; + $erroredIds = 0; foreach ($parameters as $p) { /** @var \ReflectionParameter $p */ $type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?')); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; - $autowireAttributes = $autowire ? $emptyAutowireAttributes : []; + $autowireAttributes = null; $parsedName = $p->name; $k = null; @@ -139,7 +135,7 @@ public function process(ContainerBuilder $container) $target = $arguments[$r->name][$p->name]; if ('?' !== $target[0]) { $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; - } elseif ('' === $target = (string) substr($target, 1)) { + } elseif ('' === $target = substr($target, 1)) { throw new InvalidArgumentException(\sprintf('A "controller.service_arguments" tag must have non-empty "id" attributes for service "%s".', $id)); } elseif ($p->allowsNull() && !$p->isOptional()) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; @@ -157,7 +153,7 @@ public function process(ContainerBuilder $container) $args[$p->name] = $bindingValue; continue; - } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) { + } elseif (!$autowire || (!($autowireAttributes = $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) { continue; } elseif (!$autowireAttributes && is_subclass_of($type, \UnitEnum::class)) { // do not attempt to register enum typed arguments if not already present in bindings @@ -175,10 +171,8 @@ public function process(ContainerBuilder $container) $value = $parameterBag->resolveValue($attribute->value); if ($attribute instanceof AutowireCallable) { - $value = $attribute->buildDefinition($value, $type, $p); - } - - if ($value instanceof Reference) { + $args[$p->name] = $attribute->buildDefinition($value, $type, $p); + } elseif ($value instanceof Reference) { $args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior); } else { $args[$p->name] = new Reference('.value.'.$container->hash($value)); @@ -202,6 +196,7 @@ public function process(ContainerBuilder $container) ->addError($message); $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); + ++$erroredIds; } else { $target = preg_replace('/(^|[(|&])\\\\/', '\1', $target); $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); @@ -209,7 +204,7 @@ public function process(ContainerBuilder $container) } // register the maps as a per-method service-locators if ($args) { - $controllers[$id.'::'.$r->name] = ServiceLocatorTagPass::register($container, $args); + $controllers[$id.'::'.$r->name] = ServiceLocatorTagPass::register($container, $args, \count($args) !== $erroredIds ? $id.'::'.$r->name.'()' : null); foreach ($publicAliases[$id] ?? [] as $alias) { $controllers[$alias.'::'.$r->name] = clone $controllers[$id.'::'.$r->name]; diff --git a/DependencyInjection/RegisterLocaleAwareServicesPass.php b/DependencyInjection/RegisterLocaleAwareServicesPass.php index 2a01365bd3..3c7d5ac3c9 100644 --- a/DependencyInjection/RegisterLocaleAwareServicesPass.php +++ b/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -23,10 +23,7 @@ */ class RegisterLocaleAwareServicesPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('locale_aware_listener')) { return; diff --git a/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index ef859a611b..809b9c6b36 100644 --- a/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -21,10 +21,7 @@ */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $controllerLocator = $container->findDefinition('argument_resolver.controller_locator'); $controllers = $controllerLocator->getArgument(0); @@ -32,6 +29,10 @@ public function process(ContainerBuilder $container) foreach ($controllers as $controller => $argumentRef) { $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); + if ($argumentLocator->getFactory()) { + $argumentLocator = $container->getDefinition($argumentLocator->getFactory()[0]); + } + if (!$argumentLocator->getArgument(0)) { // remove empty argument locators $reason = \sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); diff --git a/DependencyInjection/ResettableServicePass.php b/DependencyInjection/ResettableServicePass.php index 8479c8d630..1789d8bcd6 100644 --- a/DependencyInjection/ResettableServicePass.php +++ b/DependencyInjection/ResettableServicePass.php @@ -23,10 +23,7 @@ */ class ResettableServicePass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->has('services_resetter')) { return; diff --git a/DependencyInjection/ServicesResetter.php b/DependencyInjection/ServicesResetter.php index b38ab5658d..57e394fcc5 100644 --- a/DependencyInjection/ServicesResetter.php +++ b/DependencyInjection/ServicesResetter.php @@ -13,7 +13,6 @@ use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Component\VarExporter\LazyObjectInterface; -use Symfony\Contracts\Service\ResetInterface; /** * Resets provided services. @@ -21,21 +20,18 @@ * @author Alexander M. Turek * @author Nicolas Grekas * - * @internal + * @final since Symfony 7.2 */ -class ServicesResetter implements ResetInterface +class ServicesResetter implements ServicesResetterInterface { - private \Traversable $resettableServices; - private array $resetMethods; - /** * @param \Traversable $resettableServices * @param array $resetMethods */ - public function __construct(\Traversable $resettableServices, array $resetMethods) - { - $this->resettableServices = $resettableServices; - $this->resetMethods = $resetMethods; + public function __construct( + private \Traversable $resettableServices, + private array $resetMethods, + ) { } public function reset(): void @@ -49,6 +45,10 @@ public function reset(): void continue; } + if (\PHP_VERSION_ID >= 80400 && (new \ReflectionClass($service))->isUninitializedLazyObject($service)) { + continue; + } + foreach ((array) $this->resetMethods[$id] as $resetMethod) { if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) { continue; diff --git a/Tests/Fixtures/KernelForTestWithLoadClassCache.php b/DependencyInjection/ServicesResetterInterface.php similarity index 54% rename from Tests/Fixtures/KernelForTestWithLoadClassCache.php rename to DependencyInjection/ServicesResetterInterface.php index 080953fe02..88fba821db 100644 --- a/Tests/Fixtures/KernelForTestWithLoadClassCache.php +++ b/DependencyInjection/ServicesResetterInterface.php @@ -9,11 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\HttpKernel\Tests\Fixtures; +namespace Symfony\Component\HttpKernel\DependencyInjection; -class KernelForTestWithLoadClassCache extends KernelForTest +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets provided services. + */ +interface ServicesResetterInterface extends ResetInterface { - public function doLoadClassCache(): void - { - } } diff --git a/Event/ControllerArgumentsEvent.php b/Event/ControllerArgumentsEvent.php index 4c804ccf19..f0273fd099 100644 --- a/Event/ControllerArgumentsEvent.php +++ b/Event/ControllerArgumentsEvent.php @@ -29,11 +29,15 @@ final class ControllerArgumentsEvent extends KernelEvent { private ControllerEvent $controllerEvent; - private array $arguments; private array $namedArguments; - public function __construct(HttpKernelInterface $kernel, callable|ControllerEvent $controller, array $arguments, Request $request, ?int $requestType) - { + public function __construct( + HttpKernelInterface $kernel, + callable|ControllerEvent $controller, + private array $arguments, + Request $request, + ?int $requestType, + ) { parent::__construct($kernel, $request, $requestType); if (!$controller instanceof ControllerEvent) { @@ -41,7 +45,6 @@ public function __construct(HttpKernelInterface $kernel, callable|ControllerEven } $this->controllerEvent = $controller; - $this->arguments = $arguments; } public function getController(): callable diff --git a/Event/ControllerEvent.php b/Event/ControllerEvent.php index 6db2c15f9e..3d03e6de0d 100644 --- a/Event/ControllerEvent.php +++ b/Event/ControllerEvent.php @@ -98,7 +98,7 @@ public function getAttributes(?string $className = null): array } elseif (\is_string($this->controller) && false !== $i = strpos($this->controller, '::')) { $class = new \ReflectionClass(substr($this->controller, 0, $i)); } else { - $class = str_contains($this->controllerReflector->name, '{closure') ? null : (\PHP_VERSION_ID >= 80111 ? $this->controllerReflector->getClosureCalledClass() : $this->controllerReflector->getClosureScopeClass()); + $class = $this->controllerReflector instanceof \ReflectionFunction && $this->controllerReflector->isAnonymous() ? null : $this->controllerReflector->getClosureCalledClass(); } $this->attributes = []; diff --git a/Event/ExceptionEvent.php b/Event/ExceptionEvent.php index 8bc25f9c37..d4f4c4ddfd 100644 --- a/Event/ExceptionEvent.php +++ b/Event/ExceptionEvent.php @@ -32,8 +32,13 @@ final class ExceptionEvent extends RequestEvent private \Throwable $throwable; private bool $allowCustomResponseCode = false; - public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, \Throwable $e) - { + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + \Throwable $e, + private bool $isKernelTerminating = false, + ) { parent::__construct($kernel, $request, $requestType); $this->setThrowable($e); @@ -69,4 +74,9 @@ public function isAllowingCustomResponseCode(): bool { return $this->allowCustomResponseCode; } + + public function isKernelTerminating(): bool + { + return $this->isKernelTerminating; + } } diff --git a/Event/KernelEvent.php b/Event/KernelEvent.php index 02426c52a1..bc6643f0a4 100644 --- a/Event/KernelEvent.php +++ b/Event/KernelEvent.php @@ -22,19 +22,15 @@ */ class KernelEvent extends Event { - private HttpKernelInterface $kernel; - private Request $request; - private ?int $requestType; - /** * @param int $requestType The request type the kernel is currently processing; one of * HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST */ - public function __construct(HttpKernelInterface $kernel, Request $request, ?int $requestType) - { - $this->kernel = $kernel; - $this->request = $request; - $this->requestType = $requestType; + public function __construct( + private HttpKernelInterface $kernel, + private Request $request, + private ?int $requestType, + ) { } /** diff --git a/Event/RequestEvent.php b/Event/RequestEvent.php index b81a79b780..2ca242b734 100644 --- a/Event/RequestEvent.php +++ b/Event/RequestEvent.php @@ -36,10 +36,8 @@ public function getResponse(): ?Response /** * Sets a response and stops event propagation. - * - * @return void */ - public function setResponse(Response $response) + public function setResponse(Response $response): void { $this->response = $response; @@ -48,6 +46,8 @@ public function setResponse(Response $response) /** * Returns whether a response was set. + * + * @psalm-assert-if-true !null $this->getResponse() */ public function hasResponse(): bool { diff --git a/Event/ResponseEvent.php b/Event/ResponseEvent.php index 4a57d989ac..65235d0bcb 100644 --- a/Event/ResponseEvent.php +++ b/Event/ResponseEvent.php @@ -26,13 +26,13 @@ */ final class ResponseEvent extends KernelEvent { - private Response $response; - - public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, Response $response) - { + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + private Response $response, + ) { parent::__construct($kernel, $request, $requestType); - - $this->setResponse($response); } public function getResponse(): Response diff --git a/Event/TerminateEvent.php b/Event/TerminateEvent.php index 0caefdf4d5..95a0bc81f0 100644 --- a/Event/TerminateEvent.php +++ b/Event/TerminateEvent.php @@ -25,13 +25,12 @@ */ final class TerminateEvent extends KernelEvent { - private Response $response; - - public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) - { + public function __construct( + HttpKernelInterface $kernel, + Request $request, + private Response $response, + ) { parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST); - - $this->response = $response; } public function getResponse(): Response diff --git a/Event/ViewEvent.php b/Event/ViewEvent.php index 4d963aea1f..5f466b5814 100644 --- a/Event/ViewEvent.php +++ b/Event/ViewEvent.php @@ -25,15 +25,14 @@ */ final class ViewEvent extends RequestEvent { - public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent; - private mixed $controllerResult; - - public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, mixed $controllerResult, ?ControllerArgumentsEvent $controllerArgumentsEvent = null) - { + public function __construct( + HttpKernelInterface $kernel, + Request $request, + int $requestType, + private mixed $controllerResult, + public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent = null, + ) { parent::__construct($kernel, $request, $requestType); - - $this->controllerResult = $controllerResult; - $this->controllerArgumentsEvent = $controllerArgumentsEvent; } public function getControllerResult(): mixed diff --git a/EventListener/AbstractSessionListener.php b/EventListener/AbstractSessionListener.php index 1ce4905376..74534aa8ac 100644 --- a/EventListener/AbstractSessionListener.php +++ b/EventListener/AbstractSessionListener.php @@ -41,25 +41,15 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl'; /** + * @param array $sessionOptions + * * @internal */ - protected ?ContainerInterface $container; - - private bool $debug; - - /** - * @var array - */ - private array $sessionOptions; - - /** - * @internal - */ - public function __construct(?ContainerInterface $container = null, bool $debug = false, array $sessionOptions = []) - { - $this->container = $container; - $this->debug = $debug; - $this->sessionOptions = $sessionOptions; + public function __construct( + private ?ContainerInterface $container = null, + private bool $debug = false, + private array $sessionOptions = [], + ) { } /** @@ -103,7 +93,7 @@ public function onKernelRequest(RequestEvent $event): void */ public function onKernelResponse(ResponseEvent $event): void { - if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) { + if (!$event->isMainRequest()) { return; } diff --git a/EventListener/AddRequestFormatsListener.php b/EventListener/AddRequestFormatsListener.php index d4ef3fe498..f5dcdfe011 100644 --- a/EventListener/AddRequestFormatsListener.php +++ b/EventListener/AddRequestFormatsListener.php @@ -24,11 +24,9 @@ */ class AddRequestFormatsListener implements EventSubscriberInterface { - private array $formats; - - public function __construct(array $formats) - { - $this->formats = $formats; + public function __construct( + private array $formats, + ) { } /** diff --git a/EventListener/CacheAttributeListener.php b/EventListener/CacheAttributeListener.php index 723e758cd0..436e031bbb 100644 --- a/EventListener/CacheAttributeListener.php +++ b/EventListener/CacheAttributeListener.php @@ -46,10 +46,8 @@ public function __construct( /** * Handles HTTP validation headers. - * - * @return void */ - public function onKernelControllerArguments(ControllerArgumentsEvent $event) + public function onKernelControllerArguments(ControllerArgumentsEvent $event): void { $request = $event->getRequest(); @@ -92,10 +90,8 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event) /** * Modifies the response to apply HTTP cache headers when needed. - * - * @return void */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { $request = $event->getRequest(); @@ -167,6 +163,14 @@ public function onKernelResponse(ResponseEvent $event) if (false === $cache->public) { $response->setPrivate(); } + + if (true === $cache->noStore) { + $response->headers->addCacheControlDirective('no-store'); + } + + if (false === $cache->noStore) { + $response->headers->removeCacheControlDirective('no-store'); + } } } diff --git a/EventListener/DumpListener.php b/EventListener/DumpListener.php index 07a4e7e6a0..836e54dcc3 100644 --- a/EventListener/DumpListener.php +++ b/EventListener/DumpListener.php @@ -25,21 +25,14 @@ */ class DumpListener implements EventSubscriberInterface { - private ClonerInterface $cloner; - private DataDumperInterface $dumper; - private ?Connection $connection; - - public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, ?Connection $connection = null) - { - $this->cloner = $cloner; - $this->dumper = $dumper; - $this->connection = $connection; + public function __construct( + private ClonerInterface $cloner, + private DataDumperInterface $dumper, + private ?Connection $connection = null, + ) { } - /** - * @return void - */ - public function configure() + public function configure(): void { $cloner = $this->cloner; $dumper = $this->dumper; diff --git a/EventListener/ErrorListener.php b/EventListener/ErrorListener.php index 266d96cf8e..81f5dfb7fc 100644 --- a/EventListener/ErrorListener.php +++ b/EventListener/ErrorListener.php @@ -33,32 +33,23 @@ */ class ErrorListener implements EventSubscriberInterface { - protected $controller; - protected $logger; - protected $debug; /** - * @var array|null}> + * @param array|null, log_channel: string|null}> $exceptionsMapping */ - protected $exceptionsMapping; - - /** - * @param array|null}> $exceptionsMapping - */ - public function __construct(string|object|array|null $controller, ?LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) - { - $this->controller = $controller; - $this->logger = $logger; - $this->debug = $debug; - $this->exceptionsMapping = $exceptionsMapping; + public function __construct( + protected string|object|array|null $controller, + protected ?LoggerInterface $logger = null, + protected bool $debug = false, + protected array $exceptionsMapping = [], + protected array $loggers = [], + ) { } - /** - * @return void - */ - public function logKernelException(ExceptionEvent $event) + public function logKernelException(ExceptionEvent $event): void { $throwable = $event->getThrowable(); $logLevel = $this->resolveLogLevel($throwable); + $logChannel = $this->resolveLogChannel($throwable); foreach ($this->exceptionsMapping as $class => $config) { if (!$throwable instanceof $class || !$config['status_code']) { @@ -66,42 +57,33 @@ public function logKernelException(ExceptionEvent $event) } if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() !== $config['status_code']) { $headers = $throwable instanceof HttpExceptionInterface ? $throwable->getHeaders() : []; - $throwable = new HttpException($config['status_code'], $throwable->getMessage(), $throwable, $headers); + $throwable = HttpException::fromStatusCode($config['status_code'], $throwable->getMessage(), $throwable, $headers); $event->setThrowable($throwable); } break; } // There's no specific status code defined in the configuration for this exception - if (!$throwable instanceof HttpExceptionInterface) { - $class = new \ReflectionClass($throwable); - - do { - if ($attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF)) { - /** @var WithHttpStatus $instance */ - $instance = $attributes[0]->newInstance(); - - $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); - $event->setThrowable($throwable); - break; - } - } while ($class = $class->getParentClass()); + if (!$throwable instanceof HttpExceptionInterface && $withHttpStatus = $this->getInheritedAttribute($throwable::class, WithHttpStatus::class)) { + $throwable = HttpException::fromStatusCode($withHttpStatus->statusCode, $throwable->getMessage(), $throwable, $withHttpStatus->headers); + $event->setThrowable($throwable); } $e = FlattenException::createFromThrowable($throwable); - $this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel); + $this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel, $logChannel); } - /** - * @return void - */ - public function onKernelException(ExceptionEvent $event) + public function onKernelException(ExceptionEvent $event): void { if (null === $this->controller) { return; } + if (!$this->debug && $event->isKernelTerminating()) { + return; + } + $throwable = $event->getThrowable(); $exceptionHandler = set_exception_handler('var_dump'); @@ -147,10 +129,7 @@ public function removeCspHeader(ResponseEvent $event): void } } - /** - * @return void - */ - public function onControllerArguments(ControllerArgumentsEvent $event) + public function onControllerArguments(ControllerArgumentsEvent $event): void { $e = $event->getRequest()->attributes->get('exception'); @@ -182,16 +161,20 @@ public static function getSubscribedEvents(): array /** * Logs an exception. + * + * @param ?string $logChannel */ - protected function logException(\Throwable $exception, string $message, ?string $logLevel = null): void + protected function logException(\Throwable $exception, string $message, ?string $logLevel = null/* , ?string $logChannel = null */): void { - if (null === $this->logger) { - return; - } + $logChannel = (3 < \func_num_args() ? func_get_arg(3) : null) ?? $this->resolveLogChannel($exception); $logLevel ??= $this->resolveLogLevel($exception); - $this->logger->log($logLevel, $message, ['exception' => $exception]); + if (!$logger = $this->getLogger($logChannel)) { + return; + } + + $logger->log($logLevel, $message, ['exception' => $exception]); } /** @@ -205,16 +188,9 @@ private function resolveLogLevel(\Throwable $throwable): string } } - $class = new \ReflectionClass($throwable); - - do { - if ($attributes = $class->getAttributes(WithLogLevel::class)) { - /** @var WithLogLevel $instance */ - $instance = $attributes[0]->newInstance(); - - return $instance->level; - } - } while ($class = $class->getParentClass()); + if ($withLogLevel = $this->getInheritedAttribute($throwable::class, WithLogLevel::class)) { + return $withLogLevel->level; + } if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { return LogLevel::CRITICAL; @@ -223,6 +199,17 @@ private function resolveLogLevel(\Throwable $throwable): string return LogLevel::ERROR; } + private function resolveLogChannel(\Throwable $throwable): ?string + { + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && isset($config['log_channel'])) { + return $config['log_channel']; + } + } + + return null; + } + /** * Clones the request for the exception. */ @@ -231,11 +218,57 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re $attributes = [ '_controller' => $this->controller, 'exception' => $exception, - 'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger), + 'logger' => DebugLoggerConfigurator::getDebugLogger($this->getLogger($this->resolveLogChannel($exception))), ]; $request = $request->duplicate(null, null, $attributes); $request->setMethod('GET'); return $request; } + + /** + * @template T + * + * @param class-string $attribute + * + * @return T|null + */ + private function getInheritedAttribute(string $class, string $attribute): ?object + { + $class = new \ReflectionClass($class); + $interfaces = []; + $attributeReflector = null; + $parentInterfaces = []; + $ownInterfaces = []; + + do { + if ($attributes = $class->getAttributes($attribute, \ReflectionAttribute::IS_INSTANCEOF)) { + $attributeReflector = $attributes[0]; + $parentInterfaces = class_implements($class->name); + break; + } + + $interfaces[] = class_implements($class->name); + } while ($class = $class->getParentClass()); + + while ($interfaces) { + $ownInterfaces = array_diff_key(array_pop($interfaces), $parentInterfaces); + $parentInterfaces += $ownInterfaces; + + foreach ($ownInterfaces as $interface) { + $class = new \ReflectionClass($interface); + + if ($attributes = $class->getAttributes($attribute, \ReflectionAttribute::IS_INSTANCEOF)) { + $attributeReflector = $attributes[0]; + } + } + } + + return $attributeReflector?->newInstance(); + } + + private function getLogger(?string $logChannel): ?LoggerInterface + { + return $logChannel ? $this->loggers[$logChannel] ?? $this->logger : $this->logger; + } } diff --git a/EventListener/FragmentListener.php b/EventListener/FragmentListener.php index 562244b338..866ae040b5 100644 --- a/EventListener/FragmentListener.php +++ b/EventListener/FragmentListener.php @@ -33,16 +33,13 @@ */ class FragmentListener implements EventSubscriberInterface { - private UriSigner $signer; - private string $fragmentPath; - /** * @param string $fragmentPath The path that triggers this listener */ - public function __construct(UriSigner $signer, string $fragmentPath = '/_fragment') - { - $this->signer = $signer; - $this->fragmentPath = $fragmentPath; + public function __construct( + private UriSigner $signer, + private string $fragmentPath = '/_fragment', + ) { } /** @@ -70,7 +67,7 @@ public function onKernelRequest(RequestEvent $event): void } parse_str($request->query->get('_path', ''), $attributes); - $attributes['_check_controller_is_allowed'] = -1; // @deprecated, switch to true in Symfony 7 + $attributes['_check_controller_is_allowed'] = true; $request->attributes->add($attributes); $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes)); $request->query->remove('_path'); diff --git a/EventListener/LocaleAwareListener.php b/EventListener/LocaleAwareListener.php index 83fbb4f8b3..1c78c4bdd3 100644 --- a/EventListener/LocaleAwareListener.php +++ b/EventListener/LocaleAwareListener.php @@ -25,16 +25,13 @@ */ class LocaleAwareListener implements EventSubscriberInterface { - private iterable $localeAwareServices; - private RequestStack $requestStack; - /** * @param iterable $localeAwareServices */ - public function __construct(iterable $localeAwareServices, RequestStack $requestStack) - { - $this->localeAwareServices = $localeAwareServices; - $this->requestStack = $requestStack; + public function __construct( + private iterable $localeAwareServices, + private RequestStack $requestStack, + ) { } public function onKernelRequest(RequestEvent $event): void diff --git a/EventListener/LocaleListener.php b/EventListener/LocaleListener.php index 9feaa0b4f8..b905f77d4a 100644 --- a/EventListener/LocaleListener.php +++ b/EventListener/LocaleListener.php @@ -29,19 +29,13 @@ */ class LocaleListener implements EventSubscriberInterface { - private ?RequestContextAwareInterface $router; - private string $defaultLocale; - private RequestStack $requestStack; - private bool $useAcceptLanguageHeader; - private array $enabledLocales; - - public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', ?RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = []) - { - $this->defaultLocale = $defaultLocale; - $this->requestStack = $requestStack; - $this->router = $router; - $this->useAcceptLanguageHeader = $useAcceptLanguageHeader; - $this->enabledLocales = $enabledLocales; + public function __construct( + private RequestStack $requestStack, + private string $defaultLocale = 'en', + private ?RequestContextAwareInterface $router = null, + private bool $useAcceptLanguageHeader = false, + private array $enabledLocales = [], + ) { } public function setDefaultLocale(KernelEvent $event): void diff --git a/EventListener/ProfilerListener.php b/EventListener/ProfilerListener.php index 1f30582f4a..ecaaceb948 100644 --- a/EventListener/ProfilerListener.php +++ b/EventListener/ProfilerListener.php @@ -32,15 +32,9 @@ */ class ProfilerListener implements EventSubscriberInterface { - private Profiler $profiler; - private ?RequestMatcherInterface $matcher; - private bool $onlyException; - private bool $onlyMainRequests; private ?\Throwable $exception = null; /** @var \SplObjectStorage */ private \SplObjectStorage $profiles; - private RequestStack $requestStack; - private ?string $collectParameter; /** @var \SplObjectStorage */ private \SplObjectStorage $parents; @@ -48,16 +42,16 @@ class ProfilerListener implements EventSubscriberInterface * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise * @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise */ - public function __construct(Profiler $profiler, RequestStack $requestStack, ?RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMainRequests = false, ?string $collectParameter = null) - { - $this->profiler = $profiler; - $this->matcher = $matcher; - $this->onlyException = $onlyException; - $this->onlyMainRequests = $onlyMainRequests; + public function __construct( + private Profiler $profiler, + private RequestStack $requestStack, + private ?RequestMatcherInterface $matcher = null, + private bool $onlyException = false, + private bool $onlyMainRequests = false, + private ?string $collectParameter = null, + ) { $this->profiles = new \SplObjectStorage(); $this->parents = new \SplObjectStorage(); - $this->requestStack = $requestStack; - $this->collectParameter = $collectParameter; } /** diff --git a/EventListener/ResponseListener.php b/EventListener/ResponseListener.php index 5825e16e44..690bfafc69 100644 --- a/EventListener/ResponseListener.php +++ b/EventListener/ResponseListener.php @@ -24,13 +24,10 @@ */ class ResponseListener implements EventSubscriberInterface { - private string $charset; - private bool $addContentLanguageHeader; - - public function __construct(string $charset, bool $addContentLanguageHeader = false) - { - $this->charset = $charset; - $this->addContentLanguageHeader = $addContentLanguageHeader; + public function __construct( + private string $charset, + private bool $addContentLanguageHeader = false, + ) { } /** diff --git a/EventListener/RouterListener.php b/EventListener/RouterListener.php index fd8ac1a5c6..dd6f5bb214 100644 --- a/EventListener/RouterListener.php +++ b/EventListener/RouterListener.php @@ -41,30 +41,26 @@ */ class RouterListener implements EventSubscriberInterface { - private RequestMatcherInterface|UrlMatcherInterface $matcher; private RequestContext $context; - private ?LoggerInterface $logger; - private RequestStack $requestStack; - private ?string $projectDir; - private bool $debug; /** * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) * * @throws \InvalidArgumentException */ - public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $projectDir = null, bool $debug = true) - { + public function __construct( + private UrlMatcherInterface|RequestMatcherInterface $matcher, + private RequestStack $requestStack, + ?RequestContext $context = null, + private ?LoggerInterface $logger = null, + private ?string $projectDir = null, + private bool $debug = true, + ) { if (null === $context && !$matcher instanceof RequestContextAwareInterface) { throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); } - $this->matcher = $matcher; $this->context = $context ?? $matcher->getContext(); - $this->requestStack = $requestStack; - $this->logger = $logger; - $this->projectDir = $projectDir; - $this->debug = $debug; } private function setCurrentRequest(?Request $request): void @@ -114,7 +110,40 @@ public function onKernelRequest(RequestEvent $event): void 'method' => $request->getMethod(), ]); - $request->attributes->add($parameters); + $attributes = $parameters; + if ($mapping = $parameters['_route_mapping'] ?? false) { + unset($parameters['_route_mapping']); + $mappedAttributes = []; + $attributes = []; + + foreach ($parameters as $parameter => $value) { + if (!isset($mapping[$parameter])) { + $attribute = $parameter; + } elseif (\is_array($mapping[$parameter])) { + [$attribute, $parameter] = $mapping[$parameter]; + $mappedAttributes[$attribute] = ''; + } else { + $attribute = $mapping[$parameter]; + } + + if (!isset($mappedAttributes[$attribute])) { + $attributes[$attribute] = $value; + $mappedAttributes[$attribute] = $parameter; + } elseif ('' !== $mappedAttributes[$attribute]) { + $attributes[$attribute] = [ + $mappedAttributes[$attribute] => $attributes[$attribute], + $parameter => $value, + ]; + $mappedAttributes[$attribute] = ''; + } else { + $attributes[$attribute][$parameter] = $value; + } + } + + $attributes['_route_mapping'] = $mapping; + } + + $request->attributes->add($attributes); unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { diff --git a/EventListener/SessionListener.php b/EventListener/SessionListener.php index ec23a2e988..565268518f 100644 --- a/EventListener/SessionListener.php +++ b/EventListener/SessionListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\EventListener; +use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** @@ -22,6 +23,14 @@ */ class SessionListener extends AbstractSessionListener { + public function __construct( + private ?ContainerInterface $container = null, + bool $debug = false, + array $sessionOptions = [], + ) { + parent::__construct($container, $debug, $sessionOptions); + } + protected function getSession(): ?SessionInterface { if ($this->container->has('session_factory')) { diff --git a/EventListener/StreamedResponseListener.php b/EventListener/StreamedResponseListener.php deleted file mode 100644 index 312d5ee23b..0000000000 --- a/EventListener/StreamedResponseListener.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\StreamedResponse; -use Symfony\Component\HttpKernel\Event\ResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; - -trigger_deprecation('symfony/http-kernel', '6.1', 'The "%s" class is deprecated.', StreamedResponseListener::class); - -/** - * StreamedResponseListener is responsible for sending the Response - * to the client. - * - * @author Fabien Potencier - * - * @final - * - * @deprecated since Symfony 6.1 - */ -class StreamedResponseListener implements EventSubscriberInterface -{ - /** - * Filters the Response. - */ - public function onKernelResponse(ResponseEvent $event): void - { - if (!$event->isMainRequest()) { - return; - } - - $response = $event->getResponse(); - - if ($response instanceof StreamedResponse) { - $response->send(); - } - } - - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::RESPONSE => ['onKernelResponse', -1024], - ]; - } -} diff --git a/EventListener/SurrogateListener.php b/EventListener/SurrogateListener.php index a702a68f84..f6db59e36e 100644 --- a/EventListener/SurrogateListener.php +++ b/EventListener/SurrogateListener.php @@ -26,11 +26,9 @@ */ class SurrogateListener implements EventSubscriberInterface { - private ?SurrogateInterface $surrogate; - - public function __construct(?SurrogateInterface $surrogate = null) - { - $this->surrogate = $surrogate; + public function __construct( + private ?SurrogateInterface $surrogate = null, + ) { } /** diff --git a/Exception/HttpException.php b/Exception/HttpException.php index 6d2c253a33..9a71bcbd6f 100644 --- a/Exception/HttpException.php +++ b/Exception/HttpException.php @@ -18,15 +18,35 @@ */ class HttpException extends \RuntimeException implements HttpExceptionInterface { - private int $statusCode; - private array $headers; + public function __construct( + private int $statusCode, + string $message = '', + ?\Throwable $previous = null, + private array $headers = [], + int $code = 0, + ) { + parent::__construct($message, $code, $previous); + } - public function __construct(int $statusCode, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0) + public static function fromStatusCode(int $statusCode, string $message = '', ?\Throwable $previous = null, array $headers = [], int $code = 0): self { - $this->statusCode = $statusCode; - $this->headers = $headers; - - parent::__construct($message, $code, $previous); + return match ($statusCode) { + 400 => new BadRequestHttpException($message, $previous, $code, $headers), + 403 => new AccessDeniedHttpException($message, $previous, $code, $headers), + 404 => new NotFoundHttpException($message, $previous, $code, $headers), + 406 => new NotAcceptableHttpException($message, $previous, $code, $headers), + 409 => new ConflictHttpException($message, $previous, $code, $headers), + 410 => new GoneHttpException($message, $previous, $code, $headers), + 411 => new LengthRequiredHttpException($message, $previous, $code, $headers), + 412 => new PreconditionFailedHttpException($message, $previous, $code, $headers), + 423 => new LockedHttpException($message, $previous, $code, $headers), + 415 => new UnsupportedMediaTypeHttpException($message, $previous, $code, $headers), + 422 => new UnprocessableEntityHttpException($message, $previous, $code, $headers), + 428 => new PreconditionRequiredHttpException($message, $previous, $code, $headers), + 429 => new TooManyRequestsHttpException(null, $message, $previous, $code, $headers), + 503 => new ServiceUnavailableHttpException(null, $message, $previous, $code, $headers), + default => new static($statusCode, $message, $previous, $headers, $code), + }; } public function getStatusCode(): int @@ -39,10 +59,7 @@ public function getHeaders(): array return $this->headers; } - /** - * @return void - */ - public function setHeaders(array $headers) + public function setHeaders(array $headers): void { $this->headers = $headers; } diff --git a/Exception/NearMissValueResolverException.php b/Exception/NearMissValueResolverException.php new file mode 100644 index 0000000000..73ccfe916a --- /dev/null +++ b/Exception/NearMissValueResolverException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Lets value resolvers tell when an argument could be under their watch but failed to be resolved. + * + * Throwing this exception inside `ValueResolverInterface::resolve` does not interrupt the value resolvers chain. + */ +class NearMissValueResolverException extends \RuntimeException +{ +} diff --git a/Fragment/AbstractSurrogateFragmentRenderer.php b/Fragment/AbstractSurrogateFragmentRenderer.php index 7eea1aed44..ddd5bfcb07 100644 --- a/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/Fragment/AbstractSurrogateFragmentRenderer.php @@ -24,21 +24,17 @@ */ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer { - private ?SurrogateInterface $surrogate; - private FragmentRendererInterface $inlineStrategy; - private ?UriSigner $signer; - /** * The "fallback" strategy when surrogate is not available should always be an * instance of InlineFragmentRenderer. * * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported */ - public function __construct(?SurrogateInterface $surrogate, FragmentRendererInterface $inlineStrategy, ?UriSigner $signer = null) - { - $this->surrogate = $surrogate; - $this->inlineStrategy = $inlineStrategy; - $this->signer = $signer; + public function __construct( + private ?SurrogateInterface $surrogate, + private FragmentRendererInterface $inlineStrategy, + private ?UriSigner $signer = null, + ) { } /** @@ -59,7 +55,7 @@ public function __construct(?SurrogateInterface $surrogate, FragmentRendererInte public function render(string|ControllerReference $uri, Request $request, array $options = []): Response { if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { - $request->attributes->set('_check_controller_is_allowed', -1); // @deprecated, switch to true in Symfony 7 + $request->attributes->set('_check_controller_is_allowed', true); if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { throw new \InvalidArgumentException('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is not supported. Use a different rendering strategy or pass scalar values.'); diff --git a/Fragment/FragmentHandler.php b/Fragment/FragmentHandler.php index 7452424192..cfd5c27d8e 100644 --- a/Fragment/FragmentHandler.php +++ b/Fragment/FragmentHandler.php @@ -29,29 +29,27 @@ */ class FragmentHandler { - private bool $debug; + /** @var array */ private array $renderers = []; - private RequestStack $requestStack; /** * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances * @param bool $debug Whether the debug mode is enabled or not */ - public function __construct(RequestStack $requestStack, array $renderers = [], bool $debug = false) - { - $this->requestStack = $requestStack; + public function __construct( + private RequestStack $requestStack, + array $renderers = [], + private bool $debug = false, + ) { foreach ($renderers as $renderer) { $this->addRenderer($renderer); } - $this->debug = $debug; } /** * Adds a renderer. - * - * @return void */ - public function addRenderer(FragmentRendererInterface $renderer) + public function addRenderer(FragmentRendererInterface $renderer): void { $this->renderers[$renderer->getName()] = $renderer; } diff --git a/Fragment/FragmentUriGenerator.php b/Fragment/FragmentUriGenerator.php index aeb1f71239..832b0483d1 100644 --- a/Fragment/FragmentUriGenerator.php +++ b/Fragment/FragmentUriGenerator.php @@ -24,15 +24,11 @@ */ final class FragmentUriGenerator implements FragmentUriGeneratorInterface { - private string $fragmentPath; - private ?UriSigner $signer; - private ?RequestStack $requestStack; - - public function __construct(string $fragmentPath, ?UriSigner $signer = null, ?RequestStack $requestStack = null) - { - $this->fragmentPath = $fragmentPath; - $this->signer = $signer; - $this->requestStack = $requestStack; + public function __construct( + private string $fragmentPath, + private ?UriSigner $signer = null, + private ?RequestStack $requestStack = null, + ) { } public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string diff --git a/Fragment/HIncludeFragmentRenderer.php b/Fragment/HIncludeFragmentRenderer.php index 9cb01f321e..4cc77c419e 100644 --- a/Fragment/HIncludeFragmentRenderer.php +++ b/Fragment/HIncludeFragmentRenderer.php @@ -24,20 +24,15 @@ */ class HIncludeFragmentRenderer extends RoutableFragmentRenderer { - private ?string $globalDefaultTemplate; - private ?UriSigner $signer; - private ?Environment $twig; - private string $charset; - /** * @param string|null $globalDefaultTemplate The global default content (it can be a template name or the content) */ - public function __construct(?Environment $twig = null, ?UriSigner $signer = null, ?string $globalDefaultTemplate = null, string $charset = 'utf-8') - { - $this->twig = $twig; - $this->globalDefaultTemplate = $globalDefaultTemplate; - $this->signer = $signer; - $this->charset = $charset; + public function __construct( + private ?Environment $twig = null, + private ?UriSigner $signer = null, + private ?string $globalDefaultTemplate = null, + private string $charset = 'utf-8', + ) { } /** diff --git a/Fragment/InlineFragmentRenderer.php b/Fragment/InlineFragmentRenderer.php index 1999603a3b..2dbe7be602 100644 --- a/Fragment/InlineFragmentRenderer.php +++ b/Fragment/InlineFragmentRenderer.php @@ -27,13 +27,10 @@ */ class InlineFragmentRenderer extends RoutableFragmentRenderer { - private HttpKernelInterface $kernel; - private ?EventDispatcherInterface $dispatcher; - - public function __construct(HttpKernelInterface $kernel, ?EventDispatcherInterface $dispatcher = null) - { - $this->kernel = $kernel; - $this->dispatcher = $dispatcher; + public function __construct( + private HttpKernelInterface $kernel, + private ?EventDispatcherInterface $dispatcher = null, + ) { } /** @@ -103,10 +100,7 @@ public function render(string|ControllerReference $uri, Request $request, array } } - /** - * @return Request - */ - protected function createSubRequest(string $uri, Request $request) + protected function createSubRequest(string $uri, Request $request): Request { $cookies = $request->cookies->all(); $server = $request->server->all(); diff --git a/Fragment/RoutableFragmentRenderer.php b/Fragment/RoutableFragmentRenderer.php index 6a8989081f..f9b400c902 100644 --- a/Fragment/RoutableFragmentRenderer.php +++ b/Fragment/RoutableFragmentRenderer.php @@ -31,10 +31,8 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface * Sets the fragment path that triggers the fragment listener. * * @see FragmentListener - * - * @return void */ - public function setFragmentPath(string $path) + public function setFragmentPath(string $path): void { $this->fragmentPath = $path; } diff --git a/HttpCache/AbstractSurrogate.php b/HttpCache/AbstractSurrogate.php index 152aedad08..07a125af53 100644 --- a/HttpCache/AbstractSurrogate.php +++ b/HttpCache/AbstractSurrogate.php @@ -23,23 +23,13 @@ */ abstract class AbstractSurrogate implements SurrogateInterface { - protected $contentTypes; - - /** - * @deprecated since Symfony 6.3 - */ - protected $phpEscapeMap = [ - ['', '', '', ''], - ]; - /** * @param array $contentTypes An array of content-type that should be parsed for Surrogate information * (default: text/html, text/xml, application/xhtml+xml, and application/xml) */ - public function __construct(array $contentTypes = ['text/html', 'text/xml', 'application/xhtml+xml', 'application/xml']) - { - $this->contentTypes = $contentTypes; + public function __construct( + protected array $contentTypes = ['text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'], + ) { } /** @@ -59,10 +49,7 @@ public function hasSurrogateCapability(Request $request): bool return str_contains($value, \sprintf('%s/1.0', strtoupper($this->getName()))); } - /** - * @return void - */ - public function addSurrogateCapability(Request $request) + public function addSurrogateCapability(Request $request): void { $current = $request->headers->get('Surrogate-Capability'); $new = \sprintf('symfony="%s/1.0"', strtoupper($this->getName())); @@ -108,10 +95,8 @@ public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreE /** * Remove the Surrogate from the Surrogate-Control header. - * - * @return void */ - protected function removeFromControl(Response $response) + protected function removeFromControl(Response $response): void { if (!$response->headers->has('Surrogate-Control')) { return; diff --git a/HttpCache/Esi.php b/HttpCache/Esi.php index 92b41a5dd6..5866c9e593 100644 --- a/HttpCache/Esi.php +++ b/HttpCache/Esi.php @@ -32,10 +32,7 @@ public function getName(): string return 'esi'; } - /** - * @return void - */ - public function addSurrogateControl(Response $response) + public function addSurrogateControl(Response $response): void { if (str_contains($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); @@ -50,7 +47,7 @@ public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreE $alt ? \sprintf(' alt="%s"', $alt) : '' ); - if (!empty($comment)) { + if ($comment) { return \sprintf("\n%s", $comment, $html); } @@ -60,12 +57,12 @@ public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreE public function process(Request $request, Response $response): Response { $type = $response->headers->get('Content-Type'); - if (empty($type)) { + if (!$type) { $type = 'text/html'; } $parts = explode(';', $type); - if (!\in_array($parts[0], $this->contentTypes)) { + if (!\in_array($parts[0], $this->contentTypes, true)) { return $response; } diff --git a/HttpCache/HttpCache.php b/HttpCache/HttpCache.php index 8d1a3a5791..2b1be6a95a 100644 --- a/HttpCache/HttpCache.php +++ b/HttpCache/HttpCache.php @@ -32,10 +32,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface { public const BODY_EVAL_BOUNDARY_LENGTH = 24; - private HttpKernelInterface $kernel; - private StoreInterface $store; private Request $request; - private ?SurrogateInterface $surrogate; private ?ResponseCacheStrategyInterface $surrogateCacheStrategy = null; private array $options = []; private array $traces = []; @@ -84,18 +81,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * the cache can serve a stale response when an error is encountered (default: 60). * This setting is overridden by the stale-if-error HTTP Cache-Control extension * (see RFC 5861). - * - * * terminate_on_cache_hit Specifies if the kernel.terminate event should be dispatched even when the cache - * was hit (default: true). - * Unless your application needs to process events on cache hits, it is recommended - * to set this to false to avoid having to bootstrap the Symfony framework on a cache hit. */ - public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ?SurrogateInterface $surrogate = null, array $options = []) - { - $this->store = $store; - $this->kernel = $kernel; - $this->surrogate = $surrogate; - + public function __construct( + private HttpKernelInterface $kernel, + private StoreInterface $store, + private ?SurrogateInterface $surrogate = null, + array $options = [], + ) { // needed in case there is a fatal error because the backend is too slow to respond register_shutdown_function($this->store->cleanup(...)); @@ -110,7 +102,6 @@ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, 'stale_if_error' => 60, 'trace_level' => 'none', 'trace_header' => 'X-Symfony-Cache', - 'terminate_on_cache_hit' => true, ], $options); if (!isset($options['trace_level'])) { @@ -251,17 +242,12 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R return $response; } - /** - * @return void - */ - public function terminate(Request $request, Response $response) + public function terminate(Request $request, Response $response): void { // Do not call any listeners in case of a cache hit. // This ensures identical behavior as if you had a separate // reverse caching proxy such as Varnish and the like. - if ($this->options['terminate_on_cache_hit']) { - trigger_deprecation('symfony/http-kernel', '6.2', 'Setting "terminate_on_cache_hit" to "true" is deprecated and will be changed to "false" in Symfony 7.0.'); - } elseif (\in_array('fresh', $this->traces[$this->getTraceKey($request)] ?? [], true)) { + if (\in_array('fresh', $this->traces[$this->getTraceKey($request)] ?? [], true)) { return; } @@ -410,7 +396,7 @@ protected function validate(Request $request, Response $entry, bool $catch = fal // return the response and not the cache entry if the response is valid but not cached $etag = $response->getEtag(); - if ($etag && \in_array($etag, $requestEtags) && !\in_array($etag, $cachedEtags)) { + if ($etag && \in_array($etag, $requestEtags, true) && !\in_array($etag, $cachedEtags, true)) { return $response; } @@ -471,10 +457,8 @@ protected function fetch(Request $request, bool $catch = false): Response * * @param bool $catch Whether to catch exceptions or not * @param Response|null $entry A Response instance (the stale entry if present, null otherwise) - * - * @return Response */ - protected function forward(Request $request, bool $catch = false, ?Response $entry = null) + protected function forward(Request $request, bool $catch = false, ?Response $entry = null): Response { $this->surrogate?->addSurrogateCapability($request); @@ -596,11 +580,9 @@ protected function lock(Request $request, Response $entry): bool /** * Writes the Response to the cache. * - * @return void - * * @throws \Exception */ - protected function store(Request $request, Response $response) + protected function store(Request $request, Response $response): void { try { $restoreHeaders = []; @@ -675,10 +657,7 @@ private function restoreResponseBody(Request $request, Response $response): void $response->headers->remove('X-Body-File'); } - /** - * @return void - */ - protected function processResponseBody(Request $request, Response $response) + protected function processResponseBody(Request $request, Response $response): void { if ($this->surrogate?->needsParsing($response)) { $this->surrogate->process($request, $response); @@ -726,7 +705,7 @@ private function getTraceKey(Request $request): string try { return $request->getMethod().' '.$path; - } catch (SuspiciousOperationException $e) { + } catch (SuspiciousOperationException) { return '_BAD_METHOD_ '.$path; } } diff --git a/HttpCache/ResponseCacheStrategy.php b/HttpCache/ResponseCacheStrategy.php index 05670967a5..4aba46728d 100644 --- a/HttpCache/ResponseCacheStrategy.php +++ b/HttpCache/ResponseCacheStrategy.php @@ -54,10 +54,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface 'expires' => false, ]; - /** - * @return void - */ - public function add(Response $response) + public function add(Response $response): void { ++$this->embeddedResponses; @@ -113,10 +110,7 @@ public function add(Response $response) } } - /** - * @return void - */ - public function update(Response $response) + public function update(Response $response): void { // if we have no embedded Response, do nothing if (0 === $this->embeddedResponses) { diff --git a/HttpCache/ResponseCacheStrategyInterface.php b/HttpCache/ResponseCacheStrategyInterface.php index 33c8bd9412..6143a13c83 100644 --- a/HttpCache/ResponseCacheStrategyInterface.php +++ b/HttpCache/ResponseCacheStrategyInterface.php @@ -27,15 +27,11 @@ interface ResponseCacheStrategyInterface { /** * Adds a Response. - * - * @return void */ - public function add(Response $response); + public function add(Response $response): void; /** * Updates the Response HTTP headers based on the embedded Responses. - * - * @return void */ - public function update(Response $response); + public function update(Response $response): void; } diff --git a/HttpCache/Ssi.php b/HttpCache/Ssi.php index 9e0839d63d..4349e8f57e 100644 --- a/HttpCache/Ssi.php +++ b/HttpCache/Ssi.php @@ -26,10 +26,7 @@ public function getName(): string return 'ssi'; } - /** - * @return void - */ - public function addSurrogateControl(Response $response) + public function addSurrogateControl(Response $response): void { if (str_contains($response->getContent(), '