Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -1,5 +0,0 @@
vendor/
composer.lock
phpunit.xml
Tests/Fixtures/cache/
Tests/Fixtures/logs/

View File

@@ -9,11 +9,15 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller;
namespace Symfony\Component\HttpKernel\Attribute;
class NullableController
/**
* Service tag to autoconfigure controllers.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsController
{
public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory)
public function __construct()
{
}
}

View File

@@ -11,16 +11,14 @@
namespace Symfony\Component\HttpKernel\Bundle;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Console\Application;
use Symfony\Component\Finder\Finder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
* An implementation of BundleInterface that adds a few conventions
* for DependencyInjection extensions and Console commands.
* An implementation of BundleInterface that adds a few conventions for DependencyInjection extensions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -31,31 +29,27 @@ abstract class Bundle implements BundleInterface
protected $name;
protected $extension;
protected $path;
private $namespace;
private string $namespace;
/**
* Boots the Bundle.
* {@inheritdoc}
*/
public function boot()
{
}
/**
* Shutdowns the Bundle.
* {@inheritdoc}
*/
public function shutdown()
{
}
/**
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
* {@inheritdoc}
*
* This method can be overridden to register compilation passes,
* other extensions, ...
*
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function build(ContainerBuilder $container)
{
@@ -64,18 +58,16 @@ abstract class Bundle implements BundleInterface
/**
* Returns the bundle's container extension.
*
* @return ExtensionInterface|null The container extension
*
* @throws \LogicException
*/
public function getContainerExtension()
public function getContainerExtension(): ?ExtensionInterface
{
if (null === $this->extension) {
$extension = $this->createContainerExtension();
if (null !== $extension) {
if (!$extension instanceof ExtensionInterface) {
throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_class($extension)));
throw new \LogicException(sprintf('Extension "%s" must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_debug_type($extension)));
}
// check naming convention
@@ -83,10 +75,7 @@ abstract class Bundle implements BundleInterface
$expectedAlias = Container::underscore($basename);
if ($expectedAlias != $extension->getAlias()) {
throw new \LogicException(sprintf(
'Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.',
$expectedAlias, $extension->getAlias()
));
throw new \LogicException(sprintf('Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', $expectedAlias, $extension->getAlias()));
}
$this->extension = $extension;
@@ -95,19 +84,15 @@ abstract class Bundle implements BundleInterface
}
}
if ($this->extension) {
return $this->extension;
}
return $this->extension ?: null;
}
/**
* Gets the Bundle namespace.
*
* @return string The Bundle namespace
* {@inheritdoc}
*/
public function getNamespace()
public function getNamespace(): string
{
if (null === $this->namespace) {
if (!isset($this->namespace)) {
$this->parseClassName();
}
@@ -115,35 +100,22 @@ abstract class Bundle implements BundleInterface
}
/**
* Gets the Bundle directory path.
*
* @return string The Bundle absolute path
* {@inheritdoc}
*/
public function getPath()
public function getPath(): string
{
if (null === $this->path) {
$reflected = new \ReflectionObject($this);
$this->path = dirname($reflected->getFileName());
$this->path = \dirname($reflected->getFileName());
}
return $this->path;
}
/**
* Returns the bundle parent name.
*
* @return string|null The Bundle parent name it overrides or null if no parent
*/
public function getParent()
{
}
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*/
final public function getName()
final public function getName(): string
{
if (null === $this->name) {
$this->parseClassName();
@@ -152,56 +124,14 @@ abstract class Bundle implements BundleInterface
return $this->name;
}
/**
* Finds and registers Commands.
*
* Override this method if your bundle commands do not follow the conventions:
*
* * Commands are in the 'Command' sub-directory
* * Commands extend Symfony\Component\Console\Command\Command
*
* @param Application $application An Application instance
*/
public function registerCommands(Application $application)
{
if (!is_dir($dir = $this->getPath().'/Command')) {
return;
}
if (!class_exists('Symfony\Component\Finder\Finder')) {
throw new \RuntimeException('You need the symfony/finder component to register bundle commands.');
}
$finder = new Finder();
$finder->files()->name('*Command.php')->in($dir);
$prefix = $this->getNamespace().'\\Command';
foreach ($finder as $file) {
$ns = $prefix;
if ($relativePath = $file->getRelativePath()) {
$ns .= '\\'.str_replace('/', '\\', $relativePath);
}
$class = $ns.'\\'.$file->getBasename('.php');
if ($this->container) {
$commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : array();
$alias = 'console.command.'.strtolower(str_replace('\\', '_', $class));
if (isset($commandIds[$alias]) || $this->container->has($alias)) {
continue;
}
}
$r = new \ReflectionClass($class);
if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) {
$application->add($r->newInstance());
}
}
}
/**
* Returns the bundle's container extension class.
*
* @return string
*/
protected function getContainerExtensionClass()
protected function getContainerExtensionClass(): string
{
$basename = preg_replace('/Bundle$/', '', $this->getName());
@@ -210,14 +140,10 @@ abstract class Bundle implements BundleInterface
/**
* Creates the bundle's container extension.
*
* @return ExtensionInterface|null
*/
protected function createContainerExtension()
protected function createContainerExtension(): ?ExtensionInterface
{
if (class_exists($class = $this->getContainerExtensionClass())) {
return new $class();
}
return class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null;
}
private function parseClassName()

View File

@@ -36,49 +36,28 @@ interface BundleInterface extends ContainerAwareInterface
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
*
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function build(ContainerBuilder $container);
/**
* Returns the container extension that should be implicitly loaded.
*
* @return ExtensionInterface|null The default extension or null if there is none
*/
public function getContainerExtension();
/**
* Returns the bundle name that this bundle overrides.
*
* Despite its name, this method does not imply any parent/child relationship
* between the bundles, just a way to extend and override an existing
* bundle.
*
* @return string The Bundle name it overrides or null if no parent
*/
public function getParent();
public function getContainerExtension(): ?ExtensionInterface;
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*/
public function getName();
public function getName(): string;
/**
* Gets the Bundle namespace.
*
* @return string The Bundle namespace
*/
public function getNamespace();
public function getNamespace(): string;
/**
* Gets the Bundle directory path.
*
* The path should always be returned as a Unix path (with /).
*
* @return string The Bundle absolute path
*/
public function getPath();
public function getPath(): string;
}

View File

@@ -1,6 +1,198 @@
CHANGELOG
=========
6.0
---
* Remove `ArgumentInterface`
* Remove `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead
* Remove support for returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()`
* Remove `KernelEvent::isMasterRequest()`, use `isMainRequest()` instead
* Remove support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead
5.4
---
* Add the ability to enable the profiler using a request query parameter, body parameter or attribute
* Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead
* Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener`
* Add support for configuring log level, and status code by exception class
* Allow ignoring "kernel.reset" methods that don't exist with "on_invalid" attribute
5.3
---
* Deprecate `ArgumentInterface`
* Add `ArgumentMetadata::getAttributes()`
* Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead
* Mark the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal
* Deprecate returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()`
* Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement
* Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement
* Add `#[AsController]` attribute for declaring standalone controllers on PHP 8
* Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment
5.2.0
-----
* added session usage
* made the public `http_cache` service handle requests when available
* allowed enabling trusted hosts and proxies using new `kernel.trusted_hosts`,
`kernel.trusted_proxies` and `kernel.trusted_headers` parameters
* content of request parameter `_password` is now also hidden
in the request profiler raw content section
* Allowed adding attributes on controller arguments that will be passed to argument resolvers.
* kernels implementing the `ExtensionInterface` will now be auto-registered to the container
* added parameter `kernel.runtime_environment`, defined as `%env(default:kernel.environment:APP_RUNTIME_ENV)%`
* do not set a default `Accept` HTTP header when using `HttpKernelBrowser`
5.1.0
-----
* allowed to use a specific logger channel for deprecations
* made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+;
not returning an array is deprecated
* made kernels implementing `WarmableInterface` be part of the cache warmup stage
* deprecated support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead
* allowed using public aliases to reference controllers
* added session usage reporting when the `_stateless` attribute of the request is set to `true`
* added `AbstractSessionListener::onSessionUsage()` to report when the session is used while a request is stateless
5.0.0
-----
* removed support for getting the container from a non-booted kernel
* removed the first and second constructor argument of `ConfigDataCollector`
* removed `ConfigDataCollector::getApplicationName()`
* removed `ConfigDataCollector::getApplicationVersion()`
* removed support for `Symfony\Component\Templating\EngineInterface` in `HIncludeFragmentRenderer`, use a `Twig\Environment` only
* removed `TranslatorListener` in favor of `LocaleAwareListener`
* removed `getRootDir()` and `getName()` from `Kernel` and `KernelInterface`
* removed `FilterControllerArgumentsEvent`, use `ControllerArgumentsEvent` instead
* removed `FilterControllerEvent`, use `ControllerEvent` instead
* removed `FilterResponseEvent`, use `ResponseEvent` instead
* removed `GetResponseEvent`, use `RequestEvent` instead
* removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead
* removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead
* removed `PostResponseEvent`, use `TerminateEvent` instead
* removed `SaveSessionListener` in favor of `AbstractSessionListener`
* removed `Client`, use `HttpKernelBrowser` instead
* added method `getProjectDir()` to `KernelInterface`
* removed methods `serialize` and `unserialize` from `DataCollector`, store the serialized state in the data property instead
* made `ProfilerStorageInterface` internal
* removed the second and third argument of `KernelInterface::locateResource`
* removed the second and third argument of `FileLocator::__construct`
* removed loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as
fallback directories.
* removed class `ExceptionListener`, use `ErrorListener` instead
4.4.0
-----
* The `DebugHandlersListener` class has been marked as `final`
* Added new Bundle directory convention consistent with standard skeletons
* Deprecated the second and third argument of `KernelInterface::locateResource`
* Deprecated the second and third argument of `FileLocator::__construct`
* Deprecated loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as
fallback directories. Resources like service definitions are usually loaded relative to the
current directory or with a glob pattern. The fallback directories have never been advocated
so you likely do not use those in any app based on the SF Standard or Flex edition.
* Marked all dispatched event classes as `@final`
* Added `ErrorController` to enable the preview and error rendering mechanism
* Getting the container from a non-booted kernel is deprecated.
* Marked the `AjaxDataCollector`, `ConfigDataCollector`, `EventDataCollector`,
`ExceptionDataCollector`, `LoggerDataCollector`, `MemoryDataCollector`,
`RequestDataCollector` and `TimeDataCollector` classes as `@final`.
* Marked the `RouterDataCollector::collect()` method as `@final`.
* The `DataCollectorInterface::collect()` and `Profiler::collect()` methods third parameter signature
will be `\Throwable $exception = null` instead of `\Exception $exception = null` in Symfony 5.0.
* Deprecated methods `ExceptionEvent::get/setException()`, use `get/setThrowable()` instead
* Deprecated class `ExceptionListener`, use `ErrorListener` instead
4.3.0
-----
* renamed `Client` to `HttpKernelBrowser`
* `KernelInterface` doesn't extend `Serializable` anymore
* deprecated the `Kernel::serialize()` and `unserialize()` methods
* increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener`
* made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early
* deprecated `TranslatorListener` in favor of `LocaleAwareListener`
* added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener`
* made `FileLinkFormatter` final and not implement `Serializable` anymore
* the base `DataCollector` doesn't implement `Serializable` anymore, you should
store all the serialized state in the data property instead
* `DumpDataCollector` has been marked as `final`
* added an event listener to prevent search engines from indexing applications in debug mode.
* renamed `FilterControllerArgumentsEvent` to `ControllerArgumentsEvent`
* renamed `FilterControllerEvent` to `ControllerEvent`
* renamed `FilterResponseEvent` to `ResponseEvent`
* renamed `GetResponseEvent` to `RequestEvent`
* renamed `GetResponseForControllerResultEvent` to `ViewEvent`
* renamed `GetResponseForExceptionEvent` to `ExceptionEvent`
* renamed `PostResponseEvent` to `TerminateEvent`
* added `HttpClientKernel` for handling requests with an `HttpClientInterface` instance
* added `trace_header` and `trace_level` configuration options to `HttpCache`
4.2.0
-----
* deprecated `KernelInterface::getRootDir()` and the `kernel.root_dir` parameter
* deprecated `KernelInterface::getName()` and the `kernel.name` parameter
* deprecated the first and second constructor argument of `ConfigDataCollector`
* deprecated `ConfigDataCollector::getApplicationName()`
* deprecated `ConfigDataCollector::getApplicationVersion()`
4.1.0
-----
* added orphaned events support to `EventDataCollector`
* `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`)
* Added support for using `service::method` to reference controllers, making it consistent with other cases. It is recommended over the `service:action` syntax with a single colon, which will be deprecated in the future.
* Added the ability to profile individual argument value resolvers via the
`Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver`
4.0.0
-----
* removed the `DataCollector::varToString()` method, use `DataCollector::cloneVar()`
instead
* using the `DataCollector::cloneVar()` method requires the VarDumper component
* removed the `ValueExporter` class
* removed `ControllerResolverInterface::getArguments()`
* removed `TraceableControllerResolver::getArguments()`
* removed `ControllerResolver::getArguments()` and the ability to resolve arguments
* removed the `argument_resolver` service dependency from the `debug.controller_resolver`
* removed `LazyLoadingFragmentHandler::addRendererService()`
* removed `Psr6CacheClearer::addPool()`
* removed `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()`
* removed `Kernel::loadClassCache()`, `Kernel::doLoadClassCache()`, `Kernel::setClassCache()`,
and `Kernel::getEnvParameters()`
* support for the `X-Status-Code` when handling exceptions in the `HttpKernel`
has been dropped, use the `HttpKernel::allowCustomResponseCode()` method
instead
* removed convention-based commands registration
* removed the `ChainCacheClearer::add()` method
* removed the `CacheaWarmerAggregate::add()` and `setWarmers()` methods
* made `CacheWarmerAggregate` and `ChainCacheClearer` classes final
3.4.0
-----
* added a minimalist PSR-3 `Logger` class that writes in `stderr`
* made kernels implementing `CompilerPassInterface` able to process the container
* deprecated bundle inheritance
* added `RebootableInterface` and implemented it in `Kernel`
* deprecated commands auto registration
* deprecated `EnvParametersResource`
* added `Symfony\Component\HttpKernel\Client::catchExceptions()`
* deprecated the `ChainCacheClearer::add()` method
* deprecated the `CacheaWarmerAggregate::add()` and `setWarmers()` methods
* made `CacheWarmerAggregate` and `ChainCacheClearer` classes final
* added the possibility to reset the profiler to its initial state
* deprecated data collectors without a `reset()` method
* deprecated implementing `DebugLoggerInterface` without a `clear()` method
3.3.0
-----

View File

@@ -20,8 +20,6 @@ interface CacheClearerInterface
{
/**
* Clears any caches necessary.
*
* @param string $cacheDir The cache directory
*/
public function clear($cacheDir);
public function clear(string $cacheDir);
}

View File

@@ -15,20 +15,17 @@ namespace Symfony\Component\HttpKernel\CacheClearer;
* ChainCacheClearer.
*
* @author Dustin Dobervich <ddobervich@gmail.com>
*
* @final
*/
class ChainCacheClearer implements CacheClearerInterface
{
/**
* @var array
*/
protected $clearers;
private iterable $clearers;
/**
* Constructs a new instance of ChainCacheClearer.
*
* @param array $clearers The initial clearers
* @param iterable<mixed, CacheClearerInterface> $clearers
*/
public function __construct(array $clearers = array())
public function __construct(iterable $clearers = [])
{
$this->clearers = $clearers;
}
@@ -36,20 +33,10 @@ class ChainCacheClearer implements CacheClearerInterface
/**
* {@inheritdoc}
*/
public function clear($cacheDir)
public function clear(string $cacheDir)
{
foreach ($this->clearers as $clearer) {
$clearer->clear($cacheDir);
}
}
/**
* Adds a cache clearer to the aggregate.
*
* @param CacheClearerInterface $clearer
*/
public function add(CacheClearerInterface $clearer)
{
$this->clearers[] = $clearer;
}
}

View File

@@ -18,29 +18,40 @@ use Psr\Cache\CacheItemPoolInterface;
*/
class Psr6CacheClearer implements CacheClearerInterface
{
private $pools = array();
private array $pools = [];
public function __construct(array $pools = array())
/**
* @param array<string, CacheItemPoolInterface> $pools
*/
public function __construct(array $pools = [])
{
$this->pools = $pools;
}
public function addPool(CacheItemPoolInterface $pool)
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), E_USER_DEPRECATED);
$this->pools[] = $pool;
}
public function hasPool($name)
public function hasPool(string $name): bool
{
return isset($this->pools[$name]);
}
public function clearPool($name)
/**
* @throws \InvalidArgumentException If the cache pool with the given name does not exist
*/
public function getPool(string $name): CacheItemPoolInterface
{
if (!$this->hasPool($name)) {
throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name));
}
return $this->pools[$name];
}
/**
* @throws \InvalidArgumentException If the cache pool with the given name does not exist
*/
public function clearPool(string $name): bool
{
if (!isset($this->pools[$name])) {
throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name));
throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name));
}
return $this->pools[$name]->clear();
@@ -49,7 +60,7 @@ class Psr6CacheClearer implements CacheClearerInterface
/**
* {@inheritdoc}
*/
public function clear($cacheDir)
public function clear(string $cacheDir)
{
foreach ($this->pools as $pool) {
$pool->clear();

View File

@@ -18,9 +18,9 @@ namespace Symfony\Component\HttpKernel\CacheWarmer;
*/
abstract class CacheWarmer implements CacheWarmerInterface
{
protected function writeCacheFile($file, $content)
protected function writeCacheFile(string $file, $content)
{
$tmpFile = @tempnam(dirname($file), basename($file));
$tmpFile = @tempnam(\dirname($file), basename($file));
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
@chmod($file, 0666 & ~umask());

View File

@@ -15,17 +15,25 @@ namespace Symfony\Component\HttpKernel\CacheWarmer;
* Aggregates several cache warmers into a single one.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class CacheWarmerAggregate implements CacheWarmerInterface
{
protected $warmers = array();
protected $optionalsEnabled = false;
private iterable $warmers;
private bool $debug;
private ?string $deprecationLogsFilepath;
private bool $optionalsEnabled = false;
private bool $onlyOptionalsEnabled = false;
public function __construct(array $warmers = array())
/**
* @param iterable<mixed, CacheWarmerInterface> $warmers
*/
public function __construct(iterable $warmers = [], bool $debug = false, string $deprecationLogsFilepath = null)
{
foreach ($warmers as $warmer) {
$this->add($warmer);
}
$this->warmers = $warmers;
$this->debug = $debug;
$this->deprecationLogsFilepath = $deprecationLogsFilepath;
}
public function enableOptionalWarmers()
@@ -33,42 +41,86 @@ class CacheWarmerAggregate implements CacheWarmerInterface
$this->optionalsEnabled = true;
}
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
*/
public function warmUp($cacheDir)
public function enableOnlyOptionalWarmers()
{
foreach ($this->warmers as $warmer) {
if (!$this->optionalsEnabled && $warmer->isOptional()) {
continue;
}
$warmer->warmUp($cacheDir);
}
$this->onlyOptionalsEnabled = $this->optionalsEnabled = true;
}
/**
* Checks whether this warmer is optional or not.
*
* @return bool always false
* {@inheritdoc}
*/
public function isOptional()
public function warmUp(string $cacheDir): array
{
if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
$collectedLogs = [];
$previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) {
return $previousHandler ? $previousHandler($type, $message, $file, $line) : false;
}
if (isset($collectedLogs[$message])) {
++$collectedLogs[$message]['count'];
return null;
}
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3);
// Clean the trace by removing first frames added by the error handler itself.
for ($i = 0; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
$backtrace = \array_slice($backtrace, 1 + $i);
break;
}
}
$collectedLogs[$message] = [
'type' => $type,
'message' => $message,
'file' => $file,
'line' => $line,
'trace' => $backtrace,
'count' => 1,
];
return null;
});
}
$preload = [];
try {
foreach ($this->warmers as $warmer) {
if (!$this->optionalsEnabled && $warmer->isOptional()) {
continue;
}
if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) {
continue;
}
$preload[] = array_values((array) $warmer->warmUp($cacheDir));
}
} finally {
if ($collectDeprecations) {
restore_error_handler();
if (is_file($this->deprecationLogsFilepath)) {
$previousLogs = unserialize(file_get_contents($this->deprecationLogsFilepath));
if (\is_array($previousLogs)) {
$collectedLogs = array_merge($previousLogs, $collectedLogs);
}
}
file_put_contents($this->deprecationLogsFilepath, serialize(array_values($collectedLogs)));
}
}
return array_values(array_unique(array_merge([], ...$preload)));
}
/**
* {@inheritdoc}
*/
public function isOptional(): bool
{
return false;
}
public function setWarmers(array $warmers)
{
$this->warmers = array();
foreach ($warmers as $warmer) {
$this->add($warmer);
}
}
public function add(CacheWarmerInterface $warmer)
{
$this->warmers[] = $warmer;
}
}

View File

@@ -26,7 +26,7 @@ interface CacheWarmerInterface extends WarmableInterface
* A warmer should return true if the cache can be
* generated incrementally and on-demand.
*
* @return bool true if the warmer is optional, false otherwise
* @return bool
*/
public function isOptional();
}

View File

@@ -21,7 +21,7 @@ interface WarmableInterface
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
* @return string[] A list of classes or files to preload on PHP 7.4+
*/
public function warmUp($cacheDir);
public function warmUp(string $cacheDir);
}

View File

@@ -1,99 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Config;
use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
/**
* EnvParametersResource represents resources stored in prefixed environment variables.
*
* @author Chris Wilkinson <chriswilkinson84@gmail.com>
*/
class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable
{
/**
* @var string
*/
private $prefix;
/**
* @var string
*/
private $variables;
/**
* Constructor.
*
* @param string $prefix
*/
public function __construct($prefix)
{
$this->prefix = $prefix;
$this->variables = $this->findVariables();
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return serialize($this->getResource());
}
/**
* @return array An array with two keys: 'prefix' for the prefix used and 'variables' containing all the variables watched by this resource
*/
public function getResource()
{
return array('prefix' => $this->prefix, 'variables' => $this->variables);
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
return $this->findVariables() === $this->variables;
}
public function serialize()
{
return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables));
}
public function unserialize($serialized)
{
if (\PHP_VERSION_ID >= 70000) {
$unserialized = unserialize($serialized, array('allowed_classes' => false));
} else {
$unserialized = unserialize($serialized);
}
$this->prefix = $unserialized['prefix'];
$this->variables = $unserialized['variables'];
}
private function findVariables()
{
$variables = array();
foreach ($_SERVER as $key => $value) {
if (0 === strpos($key, $this->prefix)) {
$variables[$key] = $value;
}
}
ksort($variables);
return $variables;
}
}

View File

@@ -22,33 +22,23 @@ use Symfony\Component\HttpKernel\KernelInterface;
class FileLocator extends BaseFileLocator
{
private $kernel;
private $path;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param null|string $path The path the global resource directory
* @param array $paths An array of paths where to look for resources
*/
public function __construct(KernelInterface $kernel, $path = null, array $paths = array())
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
if (null !== $path) {
$this->path = $path;
$paths[] = $path;
}
parent::__construct($paths);
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function locate($file, $currentPath = null, $first = true)
public function locate(string $file, string $currentPath = null, bool $first = true): string|array
{
if (isset($file[0]) && '@' === $file[0]) {
return $this->kernel->locateResource($file, $this->path, $first);
$resource = $this->kernel->locateResource($file);
return $first ? $resource : [$resource];
}
return parent::locate($file, $currentPath, $first);

View File

@@ -28,24 +28,23 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInter
final class ArgumentResolver implements ArgumentResolverInterface
{
private $argumentMetadataFactory;
private iterable $argumentValueResolvers;
/**
* @var iterable|ArgumentValueResolverInterface[]
* @param iterable<mixed, ArgumentValueResolverInterface> $argumentValueResolvers
*/
private $argumentValueResolvers;
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = array())
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [])
{
$this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory();
$this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory();
$this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers();
}
/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
public function getArguments(Request $request, callable $controller): array
{
$arguments = array();
$arguments = [];
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
foreach ($this->argumentValueResolvers as $resolver) {
@@ -55,12 +54,14 @@ final class ArgumentResolver implements ArgumentResolverInterface
$resolved = $resolver->resolve($request, $metadata);
if (!$resolved instanceof \Generator) {
throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver)));
$atLeastOne = false;
foreach ($resolved as $append) {
$atLeastOne = true;
$arguments[] = $append;
}
foreach ($resolved as $append) {
$arguments[] = $append;
if (!$atLeastOne) {
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver)));
}
// continue to the next controller argument
@@ -69,10 +70,10 @@ final class ArgumentResolver implements ArgumentResolverInterface
$representative = $controller;
if (is_array($representative)) {
$representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]);
} elseif (is_object($representative)) {
$representative = get_class($representative);
if (\is_array($representative)) {
$representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]);
} elseif (\is_object($representative)) {
$representative = \get_class($representative);
}
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 because there is a non optional argument after this one.', $representative, $metadata->getName()));
@@ -81,14 +82,17 @@ final class ArgumentResolver implements ArgumentResolverInterface
return $arguments;
}
public static function getDefaultArgumentValueResolvers()
/**
* @return iterable<int, ArgumentValueResolverInterface>
*/
public static function getDefaultArgumentValueResolvers(): iterable
{
return array(
return [
new RequestAttributeValueResolver(),
new RequestValueResolver(),
new SessionValueResolver(),
new DefaultValueResolver(),
new VariadicValueResolver(),
);
];
}
}

View File

@@ -25,7 +25,7 @@ final class DefaultValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic());
}
@@ -33,7 +33,7 @@ final class DefaultValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null;
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
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\ControllerMetadata\ArgumentMetadata;
/**
* Provides an intuitive error message when controller fails because it is not registered as a service.
*
* @author Simeon Kolev <simeon.kolev9@gmail.com>
*/
final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
$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);
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
if (\is_array($controller = $request->attributes->get('_controller'))) {
$controller = $controller[0].'::'.$controller[1];
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller)) {
$controller = (false !== $i = strrpos($controller, ':'))
? substr($controller, 0, $i).strtolower(substr($controller, $i))
: $controller.'::__invoke';
}
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
$message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what);
throw new RuntimeException($message);
}
}

View File

@@ -25,7 +25,7 @@ final class RequestAttributeValueResolver implements ArgumentValueResolverInterf
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return !$argument->isVariadic() && $request->attributes->has($argument->getName());
}
@@ -33,7 +33,7 @@ final class RequestAttributeValueResolver implements ArgumentValueResolverInterf
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $request->attributes->get($argument->getName());
}

View File

@@ -25,7 +25,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
}
@@ -33,7 +33,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $request;
}

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
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\ControllerMetadata\ArgumentMetadata;
@@ -33,16 +34,60 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName());
$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());
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName());
if (\is_array($controller = $request->attributes->get('_controller'))) {
$controller = $controller[0].'::'.$controller[1];
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller)) {
$i = strrpos($controller, ':');
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
}
try {
yield $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());
if ($e->getMessage() === $message) {
$message = sprintf('Cannot resolve %s: %s', $what, $message);
}
$r = new \ReflectionProperty($e, 'message');
$r->setAccessible(true);
$r->setValue($e, $message);
throw $e;
}
}
}

View File

@@ -26,8 +26,12 @@ final class SessionValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
if (!$request->hasSession()) {
return false;
}
$type = $argument->getType();
if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) {
return false;
@@ -39,7 +43,7 @@ final class SessionValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $request->getSession();
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Provides timing information via the stopwatch.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class TraceableValueResolver implements ArgumentValueResolverInterface
{
private $inner;
private $stopwatch;
public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch)
{
$this->inner = $inner;
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
$method = \get_class($this->inner).'::'.__FUNCTION__;
$this->stopwatch->start($method, 'controller.argument_value_resolver');
$return = $this->inner->supports($request, $argument);
$this->stopwatch->stop($method);
return $return;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$method = \get_class($this->inner).'::'.__FUNCTION__;
$this->stopwatch->start($method, 'controller.argument_value_resolver');
yield from $this->inner->resolve($request, $argument);
$this->stopwatch->stop($method);
}
}

View File

@@ -25,7 +25,7 @@ final class VariadicValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return $argument->isVariadic() && $request->attributes->has($argument->getName());
}
@@ -33,16 +33,14 @@ final class VariadicValueResolver implements ArgumentValueResolverInterface
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$values = $request->attributes->get($argument->getName());
if (!is_array($values)) {
throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), gettype($values)));
if (!\is_array($values)) {
throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values)));
}
foreach ($values as $value) {
yield $value;
}
yield from $values;
}
}

View File

@@ -24,12 +24,7 @@ interface ArgumentResolverInterface
/**
* Returns the arguments to pass to the controller.
*
* @param Request $request
* @param callable $controller
*
* @return array An array of arguments to pass to the controller
*
* @throws \RuntimeException When no value could be provided for a required argument
*/
public function getArguments(Request $request, $controller);
public function getArguments(Request $request, callable $controller): array;
}

View File

@@ -23,21 +23,11 @@ interface ArgumentValueResolverInterface
{
/**
* Whether this resolver can resolve the value for the given ArgumentMetadata.
*
* @param Request $request
* @param ArgumentMetadata $argument
*
* @return bool
*/
public function supports(Request $request, ArgumentMetadata $argument);
public function supports(Request $request, ArgumentMetadata $argument): bool;
/**
* Returns the possible value(s).
*
* @param Request $request
* @param ArgumentMetadata $argument
*
* @return \Generator
*/
public function resolve(Request $request, ArgumentMetadata $argument);
public function resolve(Request $request, ArgumentMetadata $argument): iterable;
}

View File

@@ -13,9 +13,10 @@ namespace Symfony\Component\HttpKernel\Controller;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Container;
/**
* A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation.
* A controller resolver searching for a controller in a psr-11 container when using the "service::method" notation.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
@@ -31,46 +32,35 @@ class ContainerControllerResolver extends ControllerResolver
parent::__construct($logger);
}
/**
* Returns a callable for the given controller.
*
* @param string $controller A Controller string
*
* @return mixed A PHP callable
*
* @throws \LogicException When the name could not be parsed
* @throws \InvalidArgumentException When the controller class does not exist
*/
protected function createController($controller)
{
if (false !== strpos($controller, '::')) {
return parent::createController($controller);
}
if (1 == substr_count($controller, ':')) {
// controller in the "service:method" notation
list($service, $method) = explode(':', $controller, 2);
return array($this->container->get($service), $method);
}
if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) {
// invokable controller in the "service" notation
return $service;
}
throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller));
}
/**
* {@inheritdoc}
*/
protected function instantiateController($class)
protected function instantiateController(string $class): object
{
$class = ltrim($class, '\\');
if ($this->container->has($class)) {
return $this->container->get($class);
}
return parent::instantiateController($class);
try {
return parent::instantiateController($class);
} catch (\Error $e) {
}
$this->throwExceptionIfControllerWasRemoved($class, $e);
if ($e instanceof \ArgumentCountError) {
throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e);
}
throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e);
}
private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous)
{
if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) {
throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous);
}
}
}

View File

@@ -27,17 +27,15 @@ use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
class ControllerReference
{
public $controller;
public $attributes = array();
public $query = array();
public $attributes = [];
public $query = [];
/**
* Constructor.
*
* @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($controller, array $attributes = array(), array $query = array())
public function __construct(string $controller, array $attributes = [], array $query = [])
{
$this->controller = $controller;
$this->attributes = $attributes;

View File

@@ -15,54 +15,25 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* ControllerResolver.
*
* This implementation uses the '_controller' request attribute to determine
* the controller to execute and uses the request attributes to determine
* the controller method arguments.
* the controller to execute.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface
class ControllerResolver implements ControllerResolverInterface
{
private $logger;
/**
* If the ...$arg functionality is available.
*
* Requires at least PHP 5.6.0 or HHVM 3.9.1
*
* @var bool
*/
private $supportsVariadic;
/**
* If scalar types exists.
*
* @var bool
*/
private $supportsScalarTypes;
/**
* Constructor.
*
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
$this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType');
}
/**
* {@inheritdoc}
*
* This method looks for a '_controller' request attribute that represents
* the controller name (a string like ClassName::MethodName).
*/
public function getController(Request $request)
public function getController(Request $request): callable|false
{
if (!$controller = $request->attributes->get('_controller')) {
if (null !== $this->logger) {
@@ -72,180 +43,144 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve
return false;
}
if (is_array($controller)) {
if (\is_array($controller)) {
if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) {
try {
$controller[0] = $this->instantiateController($controller[0]);
} catch (\Error|\LogicException $e) {
if (\is_callable($controller)) {
return $controller;
}
throw $e;
}
}
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller));
}
return $controller;
}
if (is_object($controller)) {
if (method_exists($controller, '__invoke')) {
return $controller;
if (\is_object($controller)) {
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller));
}
throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo()));
return $controller;
}
if (false === strpos($controller, ':')) {
if (method_exists($controller, '__invoke')) {
return $this->instantiateController($controller);
} elseif (function_exists($controller)) {
return $controller;
}
if (\function_exists($controller)) {
return $controller;
}
$callable = $this->createController($controller);
try {
$callable = $this->createController($controller);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$e->getMessage(), 0, $e);
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable)));
if (!\is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable));
}
return $callable;
}
/**
* {@inheritdoc}
*
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead.
*/
public function getArguments(Request $request, $controller)
{
@trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && !$controller instanceof \Closure) {
$r = new \ReflectionObject($controller);
$r = $r->getMethod('__invoke');
} else {
$r = new \ReflectionFunction($controller);
}
return $this->doGetArguments($request, $controller, $r->getParameters());
}
/**
* @param Request $request
* @param callable $controller
* @param \ReflectionParameter[] $parameters
*
* @return array The arguments to use when calling the action
*
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead.
*/
protected function doGetArguments(Request $request, $controller, array $parameters)
{
@trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
$attributes = $request->attributes->all();
$arguments = array();
foreach ($parameters as $param) {
if (array_key_exists($param->name, $attributes)) {
if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) {
$arguments = array_merge($arguments, array_values($attributes[$param->name]));
} else {
$arguments[] = $attributes[$param->name];
}
} elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
$arguments[] = $request;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
} elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) {
$arguments[] = null;
} else {
if (is_array($controller)) {
$repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);
} elseif (is_object($controller)) {
$repr = get_class($controller);
} else {
$repr = $controller;
}
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name));
}
}
return $arguments;
}
/**
* Returns a callable for the given controller.
*
* @param string $controller A Controller string
*
* @return callable A PHP callable
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException When the controller cannot be created
*/
protected function createController($controller)
protected function createController(string $controller): callable
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
if (!str_contains($controller, '::')) {
$controller = $this->instantiateController($controller);
if (!\is_callable($controller)) {
throw new \InvalidArgumentException($this->getControllerError($controller));
}
return $controller;
}
list($class, $method) = explode('::', $controller, 2);
[$class, $method] = explode('::', $controller, 2);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
try {
$controller = [$this->instantiateController($class), $method];
} catch (\Error|\LogicException $e) {
try {
if ((new \ReflectionMethod($class, $method))->isStatic()) {
return $class.'::'.$method;
}
} catch (\ReflectionException $reflectionException) {
throw $e;
}
throw $e;
}
return array($this->instantiateController($class), $method);
if (!\is_callable($controller)) {
throw new \InvalidArgumentException($this->getControllerError($controller));
}
return $controller;
}
/**
* Returns an instantiated controller.
*
* @param string $class A class name
*
* @return object
*/
protected function instantiateController($class)
protected function instantiateController(string $class): object
{
return new $class();
}
private function getControllerError($callable)
private function getControllerError(mixed $callable): string
{
if (is_string($callable)) {
if (false !== strpos($callable, '::')) {
$callable = explode('::', $callable);
}
if (class_exists($callable) && !method_exists($callable, '__invoke')) {
return sprintf('Class "%s" does not have a method "__invoke".', $callable);
}
if (!function_exists($callable)) {
if (\is_string($callable)) {
if (str_contains($callable, '::')) {
$callable = explode('::', $callable, 2);
} else {
return sprintf('Function "%s" does not exist.', $callable);
}
}
if (!is_array($callable)) {
return sprintf('Invalid type for controller given, expected string or array, got "%s".', gettype($callable));
if (\is_object($callable)) {
$availableMethods = $this->getClassMethodsWithoutMagicMethods($callable);
$alternativeMsg = $availableMethods ? sprintf(' or use one of the available methods: "%s"', implode('", "', $availableMethods)) : '';
return sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', get_debug_type($callable), $alternativeMsg);
}
if (2 !== count($callable)) {
return sprintf('Invalid format for controller, expected array(controller, method) or controller::method.');
if (!\is_array($callable)) {
return sprintf('Invalid type for controller given, expected string, array or object, got "%s".', get_debug_type($callable));
}
list($controller, $method) = $callable;
if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) {
return 'Invalid array callable, expected [controller, method].';
}
if (is_string($controller) && !class_exists($controller)) {
[$controller, $method] = $callable;
if (\is_string($controller) && !class_exists($controller)) {
return sprintf('Class "%s" does not exist.', $controller);
}
$className = is_object($controller) ? get_class($controller) : $controller;
$className = \is_object($controller) ? get_debug_type($controller) : $controller;
if (method_exists($controller, $method)) {
return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className);
}
$collection = get_class_methods($controller);
$collection = $this->getClassMethodsWithoutMagicMethods($controller);
$alternatives = array();
$alternatives = [];
foreach ($collection as $item) {
$lev = levenshtein($method, $item);
if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) {
if ($lev <= \strlen($method) / 3 || str_contains($item, $method)) {
$alternatives[] = $item;
}
}
@@ -254,7 +189,7 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve
$message = sprintf('Expected method "%s" on class "%s"', $method, $className);
if (count($alternatives) > 0) {
if (\count($alternatives) > 0) {
$message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives));
} else {
$message .= sprintf('. Available methods: "%s".', implode('", "', $collection));
@@ -262,4 +197,13 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve
return $message;
}
private function getClassMethodsWithoutMagicMethods($classOrObject): array
{
$methods = get_class_methods($classOrObject);
return array_filter($methods, function (string $method) {
return 0 !== strncmp($method, '__', 2);
});
}
}

View File

@@ -17,8 +17,6 @@ use Symfony\Component\HttpFoundation\Request;
* A ControllerResolverInterface implementation knows how to determine the
* controller to execute based on a Request object.
*
* It can also determine the arguments to pass to the Controller.
*
* A Controller can be any valid PHP callable.
*
* @author Fabien Potencier <fabien@symfony.com>
@@ -31,29 +29,13 @@ interface ControllerResolverInterface
* As several resolvers can exist for a single application, a resolver must
* return false when it is not able to determine the controller.
*
* The resolver must only throw an exception when it should be able to load
* The resolver must only throw an exception when it should be able to load a
* controller but cannot because of some errors made by the developer.
*
* @param Request $request A Request instance
*
* @return callable|false A PHP callable representing the Controller,
* or false if this resolver is not able to determine the controller
*
* @throws \LogicException If the controller can't be found
* @throws \LogicException If a controller was found based on the request but it is not callable
*/
public function getController(Request $request);
/**
* Returns the arguments to pass to the controller.
*
* @param Request $request A Request instance
* @param callable $controller A PHP callable
*
* @return array An array of arguments to pass to the controller
*
* @throws \RuntimeException When value for argument given is not provided
*
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead.
*/
public function getArguments(Request $request, $controller);
public function getController(Request $request): callable|false;
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Renders error or exception pages from a given FlattenException.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ErrorController
{
private $kernel;
private string|object|array|null $controller;
private $errorRenderer;
public function __construct(HttpKernelInterface $kernel, string|object|array|null $controller, ErrorRendererInterface $errorRenderer)
{
$this->kernel = $kernel;
$this->controller = $controller;
$this->errorRenderer = $errorRenderer;
}
public function __invoke(\Throwable $exception): Response
{
$exception = $this->errorRenderer->render($exception);
return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders());
}
public function preview(Request $request, int $code): Response
{
/*
* This Request mimics the parameters set by
* \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with
* the additional "showException" flag.
*/
$subRequest = $request->duplicate(null, null, [
'_controller' => $this->controller,
'exception' => new HttpException($code, 'This is a sample exception.'),
'logger' => null,
'showException' => false,
]);
return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Fabien Potencier <fabien@symfony.com>
@@ -31,7 +31,7 @@ class TraceableArgumentResolver implements ArgumentResolverInterface
/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
public function getArguments(Request $request, callable $controller): array
{
$e = $this->stopwatch->start('controller.get_arguments');

View File

@@ -11,47 +11,27 @@
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* TraceableControllerResolver.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface
class TraceableControllerResolver implements ControllerResolverInterface
{
private $resolver;
private $stopwatch;
private $argumentResolver;
/**
* Constructor.
*
* @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
* @param ArgumentResolverInterface $argumentResolver Only required for BC
*/
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null)
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch)
{
$this->resolver = $resolver;
$this->stopwatch = $stopwatch;
$this->argumentResolver = $argumentResolver;
// BC
if (null === $this->argumentResolver) {
$this->argumentResolver = $resolver;
}
if (!$this->argumentResolver instanceof TraceableArgumentResolver) {
$this->argumentResolver = new TraceableArgumentResolver($this->argumentResolver, $this->stopwatch);
}
}
/**
* {@inheritdoc}
*/
public function getController(Request $request)
public function getController(Request $request): callable|false
{
$e = $this->stopwatch->start('controller.get_callable');
@@ -61,18 +41,4 @@ class TraceableControllerResolver implements ControllerResolverInterface, Argume
return $ret;
}
/**
* {@inheritdoc}
*
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0.
*/
public function getArguments(Request $request, $controller)
{
@trigger_error(sprintf('The %s method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), E_USER_DEPRECATED);
$ret = $this->argumentResolver->getArguments($request, $controller);
return $ret;
}
}

View File

@@ -18,22 +18,20 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata;
*/
class ArgumentMetadata
{
private $name;
private $type;
private $isVariadic;
private $hasDefaultValue;
private $defaultValue;
private $isNullable;
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 string $name
* @param string $type
* @param bool $isVariadic
* @param bool $hasDefaultValue
* @param mixed $defaultValue
* @param bool $isNullable
* @param object[] $attributes
*/
public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false)
public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, mixed $defaultValue, bool $isNullable = false, array $attributes = [])
{
$this->name = $name;
$this->type = $type;
@@ -41,14 +39,13 @@ class ArgumentMetadata
$this->hasDefaultValue = $hasDefaultValue;
$this->defaultValue = $defaultValue;
$this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue);
$this->attributes = $attributes;
}
/**
* Returns the name as given in PHP, $foo would yield "foo".
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -57,20 +54,16 @@ class ArgumentMetadata
* Returns the type of the argument.
*
* The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+.
*
* @return string
*/
public function getType()
public function getType(): ?string
{
return $this->type;
}
/**
* Returns whether the argument is defined as "...$variadic".
*
* @return bool
*/
public function isVariadic()
public function isVariadic(): bool
{
return $this->isVariadic;
}
@@ -79,20 +72,16 @@ class ArgumentMetadata
* Returns whether the argument has a default value.
*
* Implies whether an argument is optional.
*
* @return bool
*/
public function hasDefaultValue()
public function hasDefaultValue(): bool
{
return $this->hasDefaultValue;
}
/**
* Returns whether the argument accepts null values.
*
* @return bool
*/
public function isNullable()
public function isNullable(): bool
{
return $this->isNullable;
}
@@ -101,15 +90,40 @@ class ArgumentMetadata
* Returns the default value of the argument.
*
* @throws \LogicException if no default value is present; {@see self::hasDefaultValue()}
*
* @return mixed
*/
public function getDefaultValue()
public function getDefaultValue(): mixed
{
if (!$this->hasDefaultValue) {
throw new \LogicException(sprintf('Argument $%s does not have a default value. Use %s::hasDefaultValue() to avoid this exception.', $this->name, __CLASS__));
throw new \LogicException(sprintf('Argument $%s does not have a default value. Use "%s::hasDefaultValue()" to avoid this exception.', $this->name, __CLASS__));
}
return $this->defaultValue;
}
/**
* @return object[]
*/
public function getAttributes(string $name = null, int $flags = 0): array
{
if (!$name) {
return $this->attributes;
}
$attributes = [];
if ($flags & self::IS_INSTANCEOF) {
foreach ($this->attributes as $attribute) {
if ($attribute instanceof $name) {
$attributes[] = $attribute;
}
}
} else {
foreach ($this->attributes as $attribute) {
if (\get_class($attribute) === $name) {
$attributes[] = $attribute;
}
}
}
return $attributes;
}
}

View File

@@ -18,112 +18,59 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata;
*/
final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
{
/**
* If the ...$arg functionality is available.
*
* Requires at least PHP 5.6.0 or HHVM 3.9.1
*
* @var bool
*/
private $supportsVariadic;
/**
* If the reflection supports the getType() method to resolve types.
*
* Requires at least PHP 7.0.0 or HHVM 3.11.0
*
* @var bool
*/
private $supportsParameterType;
public function __construct()
{
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
$this->supportsParameterType = method_exists('ReflectionParameter', 'getType');
}
/**
* {@inheritdoc}
*/
public function createArgumentMetadata($controller)
public function createArgumentMetadata(string|object|array $controller): array
{
$arguments = array();
$arguments = [];
if (is_array($controller)) {
if (\is_array($controller)) {
$reflection = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && !$controller instanceof \Closure) {
$reflection = (new \ReflectionObject($controller))->getMethod('__invoke');
$class = $reflection->class;
} elseif (\is_object($controller) && !$controller instanceof \Closure) {
$reflection = new \ReflectionMethod($controller, '__invoke');
$class = $reflection->class;
} else {
$reflection = new \ReflectionFunction($controller);
if ($class = str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) {
$class = $class->name;
}
}
foreach ($reflection->getParameters() as $param) {
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull());
$attributes = [];
foreach ($param->getAttributes() as $reflectionAttribute) {
if (class_exists($reflectionAttribute->getName())) {
$attributes[] = $reflectionAttribute->newInstance();
}
}
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $class), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes);
}
return $arguments;
}
/**
* Returns whether an argument is variadic.
*
* @param \ReflectionParameter $parameter
*
* @return bool
*/
private function isVariadic(\ReflectionParameter $parameter)
{
return $this->supportsVariadic && $parameter->isVariadic();
}
/**
* Determines whether an argument has a default value.
*
* @param \ReflectionParameter $parameter
*
* @return bool
*/
private function hasDefaultValue(\ReflectionParameter $parameter)
{
return $parameter->isDefaultValueAvailable();
}
/**
* Returns a default value if available.
*
* @param \ReflectionParameter $parameter
*
* @return mixed|null
*/
private function getDefaultValue(\ReflectionParameter $parameter)
{
return $this->hasDefaultValue($parameter) ? $parameter->getDefaultValue() : null;
}
/**
* Returns an associated type to the given parameter if available.
*
* @param \ReflectionParameter $parameter
*
* @return null|string
*/
private function getType(\ReflectionParameter $parameter)
private function getType(\ReflectionParameter $parameter, ?string $class): ?string
{
if ($this->supportsParameterType) {
if (!$type = $parameter->getType()) {
return;
}
$typeName = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
if ('array' === $typeName && !$type->isBuiltin()) {
// Special case for HHVM with variadics
return;
}
if (!$type = $parameter->getType()) {
return null;
}
$name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
return $typeName;
if (null !== $class) {
switch (strtolower($name)) {
case 'self':
return $class;
case 'parent':
return get_parent_class($class) ?: null;
}
}
if (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $info)) {
return $info[1];
}
return $name;
}
}

View File

@@ -19,9 +19,7 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata;
interface ArgumentMetadataFactoryInterface
{
/**
* @param mixed $controller The controller to resolve the arguments for
*
* @return ArgumentMetadata[]
*/
public function createArgumentMetadata($controller);
public function createArgumentMetadata(string|object|array $controller): array;
}

View File

@@ -15,18 +15,23 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* AjaxDataCollector.
*
* @author Bart van den Burg <bart@burgov.nl>
*
* @final
*/
class AjaxDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
// all collecting is done client side
}
public function getName()
public function reset()
{
// all collecting is done client side
}
public function getName(): string
{
return 'ajax';
}

View File

@@ -11,44 +11,23 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\VarDumper\Caster\LinkStub;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\VarDumper\Caster\ClassStub;
/**
* ConfigDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface
{
/**
* @var KernelInterface
*/
private $kernel;
private $name;
private $version;
private $hasVarDumper;
/**
* Constructor.
*
* @param string $name The name of the application using the web profiler
* @param string $version The version of the application using the web profiler
*/
public function __construct($name = null, $version = null)
{
$this->name = $name;
$this->version = $version;
$this->hasVarDumper = class_exists(LinkStub::class);
}
/**
* Sets the Kernel associated with this Request.
*
* @param KernelInterface $kernel A KernelInterface instance
*/
public function setKernel(KernelInterface $kernel = null)
{
@@ -58,39 +37,36 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->data = array(
'app_name' => $this->name,
'app_version' => $this->version,
$eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE);
$eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE);
$this->data = [
'token' => $response->headers->get('X-Debug-Token'),
'symfony_version' => Kernel::VERSION,
'symfony_state' => 'unknown',
'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a',
'symfony_minor_version' => sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION),
'symfony_lts' => 4 === Kernel::MINOR_VERSION,
'symfony_state' => $this->determineSymfonyState(),
'symfony_eom' => $eom->format('F Y'),
'symfony_eol' => $eol->format('F Y'),
'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a',
'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a',
'php_version' => PHP_VERSION,
'php_architecture' => PHP_INT_SIZE * 8,
'php_intl_locale' => class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a',
'php_version' => \PHP_VERSION,
'php_architecture' => \PHP_INT_SIZE * 8,
'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'),
'apcu_enabled' => extension_loaded('apcu') && ini_get('apc.enabled'),
'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'),
'bundles' => array(),
'sapi_name' => PHP_SAPI,
);
'xdebug_enabled' => \extension_loaded('xdebug'),
'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN),
'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN),
'bundles' => [],
'sapi_name' => \PHP_SAPI,
];
if (isset($this->kernel)) {
foreach ($this->kernel->getBundles() as $name => $bundle) {
$this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath();
$this->data['bundles'][$name] = new ClassStub(\get_class($bundle));
}
$this->data['symfony_state'] = $this->determineSymfonyState();
$this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION);
$eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE);
$eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE);
$this->data['symfony_eom'] = $eom->format('F Y');
$this->data['symfony_eol'] = $eol->format('F Y');
}
if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) {
@@ -99,47 +75,40 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
public function lateCollect()
{
$this->data = $this->cloneVar($this->data);
}
public function getApplicationName()
{
return $this->data['app_name'];
}
public function getApplicationVersion()
{
return $this->data['app_version'];
}
/**
* Gets the token.
*
* @return string The token
*/
public function getToken()
public function getToken(): ?string
{
return $this->data['token'];
}
/**
* Gets the Symfony version.
*
* @return string The Symfony version
*/
public function getSymfonyVersion()
public function getSymfonyVersion(): string
{
return $this->data['symfony_version'];
}
/**
* Returns the state of the current Symfony release.
*
* @return string One of: unknown, dev, stable, eom, eol
* Returns the state of the current Symfony release
* as one of: unknown, dev, stable, eom, eol.
*/
public function getSymfonyState()
public function getSymfonyState(): string
{
return $this->data['symfony_state'];
}
@@ -147,96 +116,70 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
/**
* Returns the minor Symfony version used (without patch numbers of extra
* suffix like "RC", "beta", etc.).
*
* @return string
*/
public function getSymfonyMinorVersion()
public function getSymfonyMinorVersion(): string
{
return $this->data['symfony_minor_version'];
}
public function isSymfonyLts(): bool
{
return $this->data['symfony_lts'];
}
/**
* Returns the human redable date when this Symfony version ends its
* Returns the human readable date when this Symfony version ends its
* maintenance period.
*
* @return string
*/
public function getSymfonyEom()
public function getSymfonyEom(): string
{
return $this->data['symfony_eom'];
}
/**
* Returns the human redable date when this Symfony version reaches its
* Returns the human readable date when this Symfony version reaches its
* "end of life" and won't receive bugs or security fixes.
*
* @return string
*/
public function getSymfonyEol()
public function getSymfonyEol(): string
{
return $this->data['symfony_eol'];
}
/**
* Gets the PHP version.
*
* @return string The PHP version
*/
public function getPhpVersion()
public function getPhpVersion(): string
{
return $this->data['php_version'];
}
/**
* Gets the PHP version extra part.
*
* @return string|null The extra part
*/
public function getPhpVersionExtra()
public function getPhpVersionExtra(): ?string
{
return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null;
return $this->data['php_version_extra'] ?? null;
}
/**
* @return int The PHP architecture as number of bits (e.g. 32 or 64)
*/
public function getPhpArchitecture()
public function getPhpArchitecture(): int
{
return $this->data['php_architecture'];
}
/**
* @return string
*/
public function getPhpIntlLocale()
public function getPhpIntlLocale(): string
{
return $this->data['php_intl_locale'];
}
/**
* @return string
*/
public function getPhpTimezone()
public function getPhpTimezone(): string
{
return $this->data['php_timezone'];
}
/**
* Gets the application name.
*
* @return string The application name
*/
public function getAppName()
{
return $this->data['name'];
}
/**
* Gets the environment.
*
* @return string The environment
*/
public function getEnv()
public function getEnv(): string
{
return $this->data['env'];
}
@@ -244,39 +187,33 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
/**
* Returns true if the debug is enabled.
*
* @return bool true if debug is enabled, false otherwise
* @return bool|string true if debug is enabled, false otherwise or a string if no kernel was set
*/
public function isDebug()
public function isDebug(): bool|string
{
return $this->data['debug'];
}
/**
* Returns true if the XDebug is enabled.
*
* @return bool true if XDebug is enabled, false otherwise
*/
public function hasXDebug()
public function hasXDebug(): bool
{
return $this->data['xdebug_enabled'];
}
/**
* Returns true if APCu is enabled.
*
* @return bool true if APCu is enabled, false otherwise
*/
public function hasApcu()
public function hasApcu(): bool
{
return $this->data['apcu_enabled'];
}
/**
* Returns true if Zend OPcache is enabled.
*
* @return bool true if Zend OPcache is enabled, false otherwise
*/
public function hasZendOpcache()
public function hasZendOpcache(): bool
{
return $this->data['zend_opcache_enabled'];
}
@@ -288,10 +225,8 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
/**
* Gets the PHP SAPI name.
*
* @return string The environment
*/
public function getSapiName()
public function getSapiName(): string
{
return $this->data['sapi_name'];
}
@@ -299,21 +234,16 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'config';
}
/**
* Tries to retrieve information about the current Symfony version.
*
* @return string One of: dev, stable, eom, eol
*/
private function determineSymfonyState()
private function determineSymfonyState(): string
{
$now = new \DateTime();
$eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE)->modify('last day of this month');
$eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE)->modify('last day of this month');
$eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE)->modify('last day of this month');
$eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE)->modify('last day of this month');
if ($now > $eol) {
$versionState = 'eol';

View File

@@ -11,9 +11,8 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
use Symfony\Component\VarDumper\Caster\CutStub;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
@@ -26,96 +25,45 @@ use Symfony\Component\VarDumper\Cloner\VarCloner;
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@symfony.com>
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
abstract class DataCollector implements DataCollectorInterface
{
protected $data = array();
/**
* @var ValueExporter
* @var array|Data
*/
private $valueExporter;
protected $data = [];
/**
* @var ClonerInterface
*/
private $cloner;
public function serialize()
{
return serialize($this->data);
}
public function unserialize($data)
{
$this->data = unserialize($data);
}
/**
* Converts the variable into a serializable Data instance.
*
* This array can be displayed in the template using
* the VarDumper component.
*
* @param mixed $var
*
* @return Data
*/
protected function cloneVar($var)
protected function cloneVar(mixed $var): Data
{
if ($var instanceof Data) {
return $var;
}
if (null === $this->cloner) {
if (class_exists(CutStub::class)) {
$this->cloner = new VarCloner();
$this->cloner->setMaxItems(-1);
$this->cloner->addCasters($this->getCasters());
} else {
@trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED);
$this->cloner = false;
}
}
if (false === $this->cloner) {
if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter();
}
return $this->valueExporter->exportValue($var);
if (!isset($this->cloner)) {
$this->cloner = new VarCloner();
$this->cloner->setMaxItems(-1);
$this->cloner->addCasters($this->getCasters());
}
return $this->cloner->cloneVar($var);
}
/**
* Converts a PHP variable to a string.
*
* @param mixed $var A PHP variable
*
* @return string The string representation of the variable
*
* @deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead.
*/
protected function varToString($var)
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), E_USER_DEPRECATED);
if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter();
}
return $this->valueExporter->exportValue($var);
}
/**
* @return callable[] The casters to add to the cloner
*/
protected function getCasters()
{
return array(
$casters = [
'*' => function ($v, array $a, Stub $s, $isNested) {
if (!$v instanceof Stub) {
foreach ($a as $k => $v) {
if (is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) {
if (\is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) {
$a[$k] = new CutStub($v);
}
}
@@ -123,6 +71,31 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
return $a;
},
);
] + ReflectionCaster::UNSET_CLOSURE_FILE_INFO;
return $casters;
}
public function __sleep(): array
{
return ['data'];
}
public function __wakeup()
{
}
/**
* @internal to prevent implementing \Serializable
*/
final protected function serialize()
{
}
/**
* @internal to prevent implementing \Serializable
*/
final protected function unserialize(string $data)
{
}
}

View File

@@ -13,27 +13,24 @@ namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Service\ResetInterface;
/**
* DataCollectorInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface DataCollectorInterface
interface DataCollectorInterface extends ResetInterface
{
/**
* Collects data for the given Request and Response.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
* @param \Exception $exception An Exception instance
*/
public function collect(Request $request, Response $response, \Exception $exception = null);
public function collect(Request $request, Response $response, \Throwable $exception = null);
/**
* Returns the name of the collector.
*
* @return string The collector name
* @return string
*/
public function getName();
}

View File

@@ -14,47 +14,53 @@ namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Twig\Template;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Server\Connection;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class DumpDataCollector extends DataCollector implements DataDumperInterface
{
private $stopwatch;
private $fileLinkFormat;
private $dataCount = 0;
private $isCollected = true;
private $clonesCount = 0;
private $clonesIndex = 0;
private $rootRefs;
private $charset;
private $stopwatch = null;
private string|FileLinkFormatter|false $fileLinkFormat;
private int $dataCount = 0;
private bool $isCollected = true;
private int $clonesCount = 0;
private int $clonesIndex = 0;
private array $rootRefs;
private string $charset;
private $requestStack;
private $dumper;
private $dumperIsInjected;
private mixed $sourceContextProvider;
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null)
public function __construct(Stopwatch $stopwatch = null, string|FileLinkFormatter $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, DataDumperInterface|Connection $dumper = null)
{
$fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->stopwatch = $stopwatch;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8';
$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->dumperIsInjected = null !== $dumper;
// All clones share these properties by reference:
$this->rootRefs = array(
$this->rootRefs = [
&$this->data,
&$this->dataCount,
&$this->isCollected,
&$this->clonesCount,
);
];
$this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset);
}
public function __clone()
@@ -67,67 +73,22 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ($this->stopwatch) {
$this->stopwatch->start('dump');
}
if ($this->isCollected) {
['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext();
if ($this->dumper instanceof Connection) {
if (!$this->dumper->write($data)) {
$this->isCollected = false;
}
} elseif ($this->dumper) {
$this->doDump($this->dumper, $data, $name, $file, $line);
} else {
$this->isCollected = false;
}
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 7);
$file = $trace[0]['file'];
$line = $trace[0]['line'];
$name = false;
$fileExcerpt = false;
for ($i = 1; $i < 7; ++$i) {
if (isset($trace[$i]['class'], $trace[$i]['function'])
&& 'dump' === $trace[$i]['function']
&& 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class']
) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
while (++$i < 7) {
if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
break;
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) {
$template = $trace[$i]['object'];
$name = $template->getTemplateName();
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
$info = $template->getDebugInfo();
if (isset($info[$trace[$i - 1]['line']])) {
$line = $info[$trace[$i - 1]['line']];
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
if ($src) {
$src = explode("\n", $src);
$fileExcerpt = array();
for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) {
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>';
}
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
}
}
break;
}
}
break;
}
if (!$this->dataCount) {
$this->data = [];
}
if (false === $name) {
$name = str_replace('\\', '/', $file);
$name = substr($name, strrpos($name, '/') + 1);
}
if ($this->dumper) {
$this->doDump($data, $name, $file, $line);
}
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
++$this->dataCount;
@@ -136,10 +97,14 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
}
}
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
if (!$this->dataCount) {
$this->data = [];
}
// Sub-requests and programmatic calls stay in the collected profile.
if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) {
if ($this->dumper || ($this->requestStack && $this->requestStack->getMainRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) {
return;
}
@@ -147,67 +112,98 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if (!$this->requestStack
|| !$response->headers->has('X-Debug-Token')
|| $response->isRedirection()
|| ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
|| ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
|| 'html' !== $request->getRequestFormat()
|| false === strripos($response->getContent(), '</body>')
) {
if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
$this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
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 {
$this->dumper = new CliDumper('php://output', $this->charset);
$dumper = new CliDumper('php://output', $this->charset);
if (method_exists($dumper, 'setDisplayOptions')) {
$dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
}
}
foreach ($this->data as $dump) {
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']);
$this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']);
}
}
}
public function serialize()
public function reset()
{
if ($this->stopwatch) {
$this->stopwatch->reset();
}
$this->data = [];
$this->dataCount = 0;
$this->isCollected = true;
$this->clonesCount = 0;
$this->clonesIndex = 0;
}
/**
* @internal
*/
public function __sleep(): array
{
if (!$this->dataCount) {
$this->data = [];
}
if ($this->clonesCount !== $this->clonesIndex) {
return 'a:0:{}';
return [];
}
$this->data[] = $this->fileLinkFormat;
$this->data[] = $this->charset;
$ser = serialize($this->data);
$this->data = array();
$this->dataCount = 0;
$this->isCollected = true;
if (!$this->dumperIsInjected) {
$this->dumper = null;
}
return $ser;
return parent::__sleep();
}
public function unserialize($data)
/**
* @internal
*/
public function __wakeup()
{
parent::unserialize($data);
parent::__wakeup();
$charset = array_pop($this->data);
$fileLinkFormat = array_pop($this->data);
$this->dataCount = count($this->data);
self::__construct($this->stopwatch, $fileLinkFormat, $charset);
$this->dataCount = \count($this->data);
foreach ($this->data as $dump) {
if (!\is_string($dump['name']) || !\is_string($dump['file']) || !\is_int($dump['line'])) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
}
self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null);
}
public function getDumpsCount()
public function getDumpsCount(): int
{
return $this->dataCount;
}
public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1)
public function getDumps(string $format, int $maxDepthLimit = -1, int $maxItemsPerDepth = -1): array
{
$data = fopen('php://memory', 'r+b');
$data = fopen('php://memory', 'r+');
if ('html' === $format) {
$dumper = new HtmlDumper($data, $this->charset);
$dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
$dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format));
throw new \InvalidArgumentException(sprintf('Invalid dump format: "%s".', $format));
}
$dumps = [];
if (!$this->dataCount) {
return $this->data = [];
}
$dumps = array();
foreach ($this->data as $dump) {
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
@@ -220,51 +216,54 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
return $dumps;
}
public function getName()
public function getName(): string
{
return 'dump';
}
public function __destruct()
{
if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) {
if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) {
$this->clonesCount = 0;
$this->isCollected = true;
$h = headers_list();
$i = count($h);
array_unshift($h, 'Content-Type: '.ini_get('default_mimetype'));
$i = \count($h);
array_unshift($h, 'Content-Type: '.\ini_get('default_mimetype'));
while (0 !== stripos($h[$i], 'Content-Type:')) {
--$i;
}
if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
$this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && stripos($h[$i], 'html')) {
$dumper = new HtmlDumper('php://output', $this->charset);
$dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
$dumper = new CliDumper('php://output', $this->charset);
if (method_exists($dumper, 'setDisplayOptions')) {
$dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
}
}
foreach ($this->data as $i => $dump) {
$this->data[$i] = null;
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']);
$this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']);
}
$this->data = array();
$this->data = [];
$this->dataCount = 0;
}
}
private function doDump($data, $name, $file, $line)
private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line)
{
if ($this->dumper instanceof CliDumper) {
if ($dumper instanceof CliDumper) {
$contextDumper = function ($name, $file, $line, $fmt) {
if ($this instanceof HtmlDumper) {
if ($file) {
$s = $this->style('meta', '%s');
$f = strip_tags($this->style('', $file));
$name = strip_tags($this->style('', $name));
if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $file, '%l' => $line)) : $fmt->format($file, $line)) {
if ($fmt && $link = \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line)) {
$name = sprintf('<a href="%s" title="%s">'.$s.'</a>', strip_tags($this->style('', $link)), $f, $name);
} else {
$name = sprintf('<abbr title="%s">'.$s.'</abbr>', $f, $name);
@@ -278,26 +277,12 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
}
$this->dumpLine(0);
};
$contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper);
$contextDumper = $contextDumper->bindTo($dumper, $dumper);
$contextDumper($name, $file, $line, $this->fileLinkFormat);
} else {
$cloner = new VarCloner();
$this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':'));
$dumper->dump($cloner->cloneVar($name.' on line '.$line.':'));
}
$this->dumper->dump($data);
}
private function htmlEncode($s)
{
$html = '';
$dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset);
$dumper->setDumpHeader('');
$dumper->setDumpBoundaries('', '');
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar($s));
return substr(strip_tags($html), 1, -1);
$dumper->dump($data);
}
}

View File

@@ -11,51 +11,68 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* EventDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see TraceableEventDispatcher
*
* @final
*/
class EventDataCollector extends DataCollector implements LateDataCollectorInterface
{
protected $dispatcher;
private $dispatcher;
private $requestStack;
private $currentRequest = null;
public function __construct(EventDispatcherInterface $dispatcher = null)
public function __construct(EventDispatcherInterface $dispatcher = null, RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->data = array(
'called_listeners' => array(),
'not_called_listeners' => array(),
);
$this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null;
$this->data = [
'called_listeners' => [],
'not_called_listeners' => [],
'orphaned_events' => [],
];
}
public function reset()
{
$this->data = [];
if ($this->dispatcher instanceof ResetInterface) {
$this->dispatcher->reset();
}
}
public function lateCollect()
{
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
$this->setCalledListeners($this->dispatcher->getCalledListeners());
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
if ($this->dispatcher instanceof TraceableEventDispatcher) {
$this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest));
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest));
$this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest));
}
$this->data = $this->cloneVar($this->data);
}
/**
* Sets the called listeners.
*
* @param array $listeners An array of called listeners
*
* @see TraceableEventDispatcherInterface
* @see TraceableEventDispatcher
*/
public function setCalledListeners(array $listeners)
{
@@ -63,23 +80,15 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
}
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*
* @see TraceableEventDispatcherInterface
* @see TraceableEventDispatcher
*/
public function getCalledListeners()
public function getCalledListeners(): array|Data
{
return $this->data['called_listeners'];
}
/**
* Sets the not called listeners.
*
* @param array $listeners An array of not called listeners
*
* @see TraceableEventDispatcherInterface
* @see TraceableEventDispatcher
*/
public function setNotCalledListeners(array $listeners)
{
@@ -87,21 +96,35 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
}
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*
* @see TraceableEventDispatcherInterface
* @see TraceableEventDispatcher
*/
public function getNotCalledListeners()
public function getNotCalledListeners(): array|Data
{
return $this->data['not_called_listeners'];
}
/**
* @param array $events An array of orphaned events
*
* @see TraceableEventDispatcher
*/
public function setOrphanedEvents(array $events)
{
$this->data['orphaned_events'] = $events;
}
/**
* @see TraceableEventDispatcher
*/
public function getOrphanedEvents(): array|Data
{
return $this->data['orphaned_events'];
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'events';
}

View File

@@ -11,85 +11,63 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* ExceptionDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ExceptionDataCollector extends DataCollector
{
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
if (null !== $exception) {
$this->data = array(
'exception' => FlattenException::create($exception),
);
$this->data = [
'exception' => FlattenException::createFromThrowable($exception),
];
}
}
/**
* Checks if the exception is not null.
*
* @return bool true if the exception is not null, false otherwise
* {@inheritdoc}
*/
public function hasException()
public function reset()
{
$this->data = [];
}
public function hasException(): bool
{
return isset($this->data['exception']);
}
/**
* Gets the exception.
*
* @return \Exception The exception
*/
public function getException()
public function getException(): \Exception|FlattenException
{
return $this->data['exception'];
}
/**
* Gets the exception message.
*
* @return string The exception message
*/
public function getMessage()
public function getMessage(): string
{
return $this->data['exception']->getMessage();
}
/**
* Gets the exception code.
*
* @return int The exception code
*/
public function getCode()
public function getCode(): int
{
return $this->data['exception']->getCode();
}
/**
* Gets the status code.
*
* @return int The status code
*/
public function getStatusCode()
public function getStatusCode(): int
{
return $this->data['exception']->getStatusCode();
}
/**
* Gets the exception trace.
*
* @return array The exception trace
*/
public function getTrace()
public function getTrace(): array
{
return $this->data['exception']->getTrace();
}
@@ -97,7 +75,7 @@ class ExceptionDataCollector extends DataCollector
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'exception';
}

View File

@@ -11,36 +11,52 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* LogDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $logger;
private $containerPathPrefix;
private ?string $containerPathPrefix;
private $currentRequest = null;
private $requestStack;
private ?array $processedLogs = null;
public function __construct($logger = null, $containerPathPrefix = null)
public function __construct(object $logger = null, string $containerPathPrefix = null, RequestStack $requestStack = null)
{
if (null !== $logger && $logger instanceof DebugLoggerInterface) {
if ($logger instanceof DebugLoggerInterface) {
$this->logger = $logger;
}
$this->containerPathPrefix = $containerPathPrefix;
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
// everything is done as late as possible
$this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null;
}
/**
* {@inheritdoc}
*/
public function reset()
{
if (isset($this->logger)) {
$this->logger->clear();
}
$this->data = [];
}
/**
@@ -48,77 +64,156 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
*/
public function lateCollect()
{
if (null !== $this->logger) {
if (isset($this->logger)) {
$containerDeprecationLogs = $this->getContainerDeprecationLogs();
$this->data = $this->computeErrorsCount($containerDeprecationLogs);
$this->data['compiler_logs'] = $this->getContainerCompilerLogs();
$this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs));
// get compiler logs later (only when they are needed) to improve performance
$this->data['compiler_logs'] = [];
$this->data['compiler_logs_filepath'] = $this->containerPathPrefix.'Compiler.log';
$this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs($this->currentRequest), $containerDeprecationLogs));
$this->data = $this->cloneVar($this->data);
}
$this->currentRequest = null;
}
/**
* Gets the logs.
*
* @return array An array of logs
*/
public function getLogs()
{
return isset($this->data['logs']) ? $this->data['logs'] : array();
return $this->data['logs'] ?? [];
}
public function getProcessedLogs()
{
if (null !== $this->processedLogs) {
return $this->processedLogs;
}
$rawLogs = $this->getLogs();
if ([] === $rawLogs) {
return $this->processedLogs = $rawLogs;
}
$logs = [];
foreach ($this->getLogs()->getValue() as $rawLog) {
$rawLogData = $rawLog->getValue();
if ($rawLogData['priority']->getValue() > 300) {
$logType = 'error';
} elseif (isset($rawLogData['scream']) && false === $rawLogData['scream']->getValue()) {
$logType = 'deprecation';
} elseif (isset($rawLogData['scream']) && true === $rawLogData['scream']->getValue()) {
$logType = 'silenced';
} else {
$logType = 'regular';
}
$logs[] = [
'type' => $logType,
'errorCount' => $rawLog['errorCount'] ?? 1,
'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(),
'priority' => $rawLogData['priority']->getValue(),
'priorityName' => $rawLogData['priorityName']->getValue(),
'channel' => $rawLogData['channel']->getValue(),
'message' => $rawLogData['message'],
'context' => $rawLogData['context'],
];
}
// sort logs from oldest to newest
usort($logs, static function ($logA, $logB) {
return $logA['timestamp'] <=> $logB['timestamp'];
});
return $this->processedLogs = $logs;
}
public function getFilters()
{
$filters = [
'channel' => [],
'priority' => [
'Debug' => 100,
'Info' => 200,
'Notice' => 250,
'Warning' => 300,
'Error' => 400,
'Critical' => 500,
'Alert' => 550,
'Emergency' => 600,
],
];
$allChannels = [];
foreach ($this->getProcessedLogs() as $log) {
if ('' === trim($log['channel'] ?? '')) {
continue;
}
$allChannels[] = $log['channel'];
}
$channels = array_unique($allChannels);
sort($channels);
$filters['channel'] = $channels;
return $filters;
}
public function getPriorities()
{
return isset($this->data['priorities']) ? $this->data['priorities'] : array();
return $this->data['priorities'] ?? [];
}
public function countErrors()
{
return isset($this->data['error_count']) ? $this->data['error_count'] : 0;
return $this->data['error_count'] ?? 0;
}
public function countDeprecations()
{
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
return $this->data['deprecation_count'] ?? 0;
}
public function countWarnings()
{
return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0;
return $this->data['warning_count'] ?? 0;
}
public function countScreams()
{
return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0;
return $this->data['scream_count'] ?? 0;
}
public function getCompilerLogs()
{
return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : array();
return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null));
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'logger';
}
private function getContainerDeprecationLogs()
private function getContainerDeprecationLogs(): array
{
if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) {
return array();
if (null === $this->containerPathPrefix || !is_file($file = $this->containerPathPrefix.'Deprecations.log')) {
return [];
}
if ('' === $logContent = trim(file_get_contents($file))) {
return [];
}
$bootTime = filemtime($file);
$logs = array();
foreach (unserialize(file_get_contents($file)) as $log) {
$log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count']));
$logs = [];
foreach (unserialize($logContent) as $log) {
$log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])];
$log['timestamp'] = $bootTime;
$log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED);
$log['priority'] = 100;
$log['priorityName'] = 'DEBUG';
$log['channel'] = '-';
$log['channel'] = null;
$log['scream'] = false;
unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']);
$logs[] = $log;
@@ -127,28 +222,29 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
return $logs;
}
private function getContainerCompilerLogs()
private function getContainerCompilerLogs(string $compilerLogsFilepath = null): array
{
if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) {
return array();
if (!is_file($compilerLogsFilepath)) {
return [];
}
$logs = array();
foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) {
$logs = [];
foreach (file($compilerLogsFilepath, \FILE_IGNORE_NEW_LINES) as $log) {
$log = explode(': ', $log, 2);
if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) {
$log = array('Unknown Compiler Pass', implode(': ', $log));
$log = ['Unknown Compiler Pass', implode(': ', $log)];
}
$logs[$log[0]][] = array('message' => $log[1]);
$logs[$log[0]][] = ['message' => $log[1]];
}
return $logs;
}
private function sanitizeLogs($logs)
private function sanitizeLogs(array $logs)
{
$sanitizedLogs = array();
$sanitizedLogs = [];
$silencedLogs = [];
foreach ($logs as $log) {
if (!$this->isSilencedOrDeprecationErrorLog($log)) {
@@ -157,7 +253,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
continue;
}
$message = $log['message'];
$message = '_'.$log['message'];
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
@@ -167,10 +263,10 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
$silencedLogs[$h] = true;
if (!isset($sanitizedLogs[$message])) {
$sanitizedLogs[$message] = $log + array(
$sanitizedLogs[$message] = $log + [
'errorCount' => 0,
'scream' => true,
);
];
}
$sanitizedLogs[$message]['errorCount'] += $exception->count;
@@ -182,10 +278,10 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
if (isset($sanitizedLogs[$errorId])) {
++$sanitizedLogs[$errorId]['errorCount'];
} else {
$log += array(
$log += [
'errorCount' => 1,
'scream' => false,
);
];
$sanitizedLogs[$errorId] = $log;
}
@@ -194,7 +290,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
return array_values($sanitizedLogs);
}
private function isSilencedOrDeprecationErrorLog(array $log)
private function isSilencedOrDeprecationErrorLog(array $log): bool
{
if (!isset($log['context']['exception'])) {
return false;
@@ -206,32 +302,32 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
return true;
}
if ($exception instanceof \ErrorException && in_array($exception->getSeverity(), array(E_DEPRECATED, E_USER_DEPRECATED), true)) {
if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [\E_DEPRECATED, \E_USER_DEPRECATED], true)) {
return true;
}
return false;
}
private function computeErrorsCount(array $containerDeprecationLogs)
private function computeErrorsCount(array $containerDeprecationLogs): array
{
$silencedLogs = array();
$count = array(
'error_count' => $this->logger->countErrors(),
$silencedLogs = [];
$count = [
'error_count' => $this->logger->countErrors($this->currentRequest),
'deprecation_count' => 0,
'warning_count' => 0,
'scream_count' => 0,
'priorities' => array(),
);
'priorities' => [],
];
foreach ($this->logger->getLogs() as $log) {
foreach ($this->logger->getLogs($this->currentRequest) as $log) {
if (isset($count['priorities'][$log['priority']])) {
++$count['priorities'][$log['priority']]['count'];
} else {
$count['priorities'][$log['priority']] = array(
$count['priorities'][$log['priority']] = [
'count' => 1,
'name' => $log['priorityName'],
);
];
}
if ('WARNING' === $log['priorityName']) {
++$count['warning_count'];

View File

@@ -15,28 +15,36 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* MemoryDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface
{
public function __construct()
{
$this->data = array(
'memory' => 0,
'memory_limit' => $this->convertToBytes(ini_get('memory_limit')),
);
$this->reset();
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->updateMemoryUsage();
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [
'memory' => 0,
'memory_limit' => $this->convertToBytes(\ini_get('memory_limit')),
];
}
/**
* {@inheritdoc}
*/
@@ -45,29 +53,16 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
$this->updateMemoryUsage();
}
/**
* Gets the memory.
*
* @return int The memory
*/
public function getMemory()
public function getMemory(): int
{
return $this->data['memory'];
}
/**
* Gets the PHP memory limit.
*
* @return int The memory limit
*/
public function getMemoryLimit()
public function getMemoryLimit(): int|float
{
return $this->data['memory_limit'];
}
/**
* Updates the memory usage data.
*/
public function updateMemoryUsage()
{
$this->data['memory'] = memory_get_peak_usage(true);
@@ -76,12 +71,12 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'memory';
}
private function convertToBytes($memoryLimit)
private function convertToBytes(string $memoryLimit): int|float
{
if ('-1' === $memoryLimit) {
return -1;
@@ -89,18 +84,21 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
$memoryLimit = strtolower($memoryLimit);
$max = strtolower(ltrim($memoryLimit, '+'));
if (0 === strpos($max, '0x')) {
$max = intval($max, 16);
} elseif (0 === strpos($max, '0')) {
$max = intval($max, 8);
if (str_starts_with($max, '0x')) {
$max = \intval($max, 16);
} elseif (str_starts_with($max, '0')) {
$max = \intval($max, 8);
} else {
$max = (int) $max;
}
switch (substr($memoryLimit, -1)) {
case 't': $max *= 1024;
// no break
case 'g': $max *= 1024;
// no break
case 'm': $max *= 1024;
// no break
case 'k': $max *= 1024;
}

View File

@@ -11,63 +11,66 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* RequestDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface
{
/** @var \SplObjectStorage */
protected $controllers;
/**
* @var \SplObjectStorage<Request, callable>
*/
private \SplObjectStorage $controllers;
private array $sessionUsages = [];
private $requestStack;
public function __construct()
public function __construct(RequestStack $requestStack = null)
{
$this->controllers = new \SplObjectStorage();
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
// attributes are serialized and as they can be anything, they need to be converted to strings.
$attributes = array();
$attributes = [];
$route = '';
foreach ($request->attributes->all() as $key => $value) {
if ('_route' === $key) {
$route = is_object($value) ? $value->getPath() : $value;
$route = \is_object($value) ? $value->getPath() : $value;
$attributes[$key] = $route;
} else {
$attributes[$key] = $value;
}
}
$content = null;
try {
$content = $request->getContent();
} catch (\LogicException $e) {
// the user already got the request content as a resource
$content = false;
}
$content = $request->getContent();
$sessionMetadata = array();
$sessionAttributes = array();
$session = null;
$flashes = array();
$sessionMetadata = [];
$sessionAttributes = [];
$flashes = [];
if ($request->hasSession()) {
$session = $request->getSession();
if ($session->isStarted()) {
$sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated());
$sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed());
$sessionMetadata['Created'] = date(\DATE_RFC822, $session->getMetadataBag()->getCreated());
$sessionMetadata['Last used'] = date(\DATE_RFC822, $session->getMetadataBag()->getLastUsed());
$sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime();
$sessionAttributes = $session->all();
$flashes = $session->getFlashBag()->peekAll();
@@ -76,20 +79,27 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$statusCode = $response->getStatusCode();
$responseCookies = array();
$responseCookies = [];
foreach ($response->headers->getCookies() as $cookie) {
$responseCookies[$cookie->getName()] = $cookie;
}
$this->data = array(
$dotenvVars = [];
foreach (explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '') as $name) {
if ('' !== $name && isset($_ENV[$name])) {
$dotenvVars[$name] = $_ENV[$name];
}
}
$this->data = [
'method' => $request->getMethod(),
'format' => $request->getRequestFormat(),
'content' => $content,
'content_type' => $response->headers->get('Content-Type', 'text/html'),
'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '',
'status_text' => Response::$statusTexts[$statusCode] ?? '',
'status_code' => $statusCode,
'request_query' => $request->query->all(),
'request_request' => $request->request->all(),
'request_files' => $request->files->all(),
'request_headers' => $request->headers->all(),
'request_server' => $request->server->all(),
'request_cookies' => $request->cookies->all(),
@@ -99,11 +109,14 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
'response_cookies' => $responseCookies,
'session_metadata' => $sessionMetadata,
'session_attributes' => $sessionAttributes,
'session_usages' => array_values($this->sessionUsages),
'stateless_check' => $this->requestStack?->getMainRequest()?->attributes->get('_stateless') ?? false,
'flashes' => $flashes,
'path_info' => $request->getPathInfo(),
'controller' => 'n/a',
'locale' => $request->getLocale(),
);
'dotenv_vars' => $dotenvVars,
];
if (isset($this->data['request_headers']['php-auth-pw'])) {
$this->data['request_headers']['php-auth-pw'] = '******';
@@ -114,11 +127,15 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
}
if (isset($this->data['request_request']['_password'])) {
$encodedPassword = rawurlencode($this->data['request_request']['_password']);
$content = str_replace('_password='.$encodedPassword, '_password=******', $content);
$this->data['request_request']['_password'] = '******';
}
$this->data['content'] = $content;
foreach ($this->data as $key => $value) {
if (!is_array($value)) {
if (!\is_array($value)) {
continue;
}
if ('request_headers' === $key || 'response_headers' === $key) {
@@ -131,24 +148,32 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
unset($this->controllers[$request]);
}
if (null !== $session && $session->isStarted()) {
if ($request->attributes->has('_redirected')) {
$this->data['redirect'] = $session->remove('sf_redirect');
}
if ($request->attributes->has('_redirected') && $redirectCookie = $request->cookies->get('sf_redirect')) {
$this->data['redirect'] = json_decode($redirectCookie, true);
if ($response->isRedirect()) {
$session->set('sf_redirect', array(
$response->headers->clearCookie('sf_redirect');
}
if ($response->isRedirect()) {
$response->headers->setCookie(new Cookie(
'sf_redirect',
json_encode([
'token' => $response->headers->get('x-debug-token'),
'route' => $request->attributes->get('_route', 'n/a'),
'method' => $request->getMethod(),
'controller' => $this->parseController($request->attributes->get('_controller')),
'status_code' => $statusCode,
'status_text' => Response::$statusTexts[(int) $statusCode],
));
}
'status_text' => Response::$statusTexts[$statusCode],
]),
0, '/', null, $request->isSecure(), true, false, 'lax'
));
}
$this->data['identifier'] = $this->data['route'] ?: (is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']);
$this->data['identifier'] = $this->data['route'] ?: (\is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']);
if ($response->headers->has('x-previous-debug-token')) {
$this->data['forward_token'] = $response->headers->get('x-previous-debug-token');
}
}
public function lateCollect()
@@ -156,6 +181,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$this->data = $this->cloneVar($this->data);
}
public function reset()
{
$this->data = [];
$this->controllers = new \SplObjectStorage();
$this->sessionUsages = [];
}
public function getMethod()
{
return $this->data['method'];
@@ -176,17 +208,22 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
return new ParameterBag($this->data['request_query']->getValue());
}
public function getRequestFiles()
{
return new ParameterBag($this->data['request_files']->getValue());
}
public function getRequestHeaders()
{
return new ParameterBag($this->data['request_headers']->getValue());
}
public function getRequestServer($raw = false)
public function getRequestServer(bool $raw = false)
{
return new ParameterBag($this->data['request_server']->getValue($raw));
}
public function getRequestCookies($raw = false)
public function getRequestCookies(bool $raw = false)
{
return new ParameterBag($this->data['request_cookies']->getValue($raw));
}
@@ -216,6 +253,16 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
return $this->data['session_attributes']->getValue();
}
public function getStatelessCheck()
{
return $this->data['stateless_check'];
}
public function getSessionUsages()
{
return $this->data['session_usages'];
}
public function getFlashes()
{
return $this->data['flashes']->getValue();
@@ -226,6 +273,18 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
return $this->data['content'];
}
public function isJsonRequest()
{
return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']);
}
public function getPrettyJson()
{
$decoded = json_decode($this->getContent());
return \JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, \JSON_PRETTY_PRINT) : null;
}
public function getContentType()
{
return $this->data['content_type'];
@@ -251,14 +310,17 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
return $this->data['locale'];
}
public function getDotenvVars()
{
return new ParameterBag($this->data['dotenv_vars']->getValue());
}
/**
* Gets the route name.
*
* The _route request attributes is automatically set by the Router Matcher.
*
* @return string The route
*/
public function getRoute()
public function getRoute(): string
{
return $this->data['route'];
}
@@ -272,21 +334,19 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
* Gets the route parameters.
*
* The _route_params request attributes is automatically set by the RouterListener.
*
* @return array The parameters
*/
public function getRouteParams()
public function getRouteParams(): array
{
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : array();
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : [];
}
/**
* Gets the parsed controller.
*
* @return array|string The controller as a string or array of data
* with keys 'class', 'method', 'file' and 'line'
* @return array|string|Data The controller as a string or array of data
* with keys 'class', 'method', 'file' and 'line'
*/
public function getController()
public function getController(): array|string|Data
{
return $this->data['controller'];
}
@@ -294,78 +354,110 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
/**
* Gets the previous request attributes.
*
* @return array|bool A legacy array of data from the previous redirection response
* or false otherwise
* @return array|Data|false A legacy array of data from the previous redirection response
* or false otherwise
*/
public function getRedirect()
public function getRedirect(): array|Data|false
{
return isset($this->data['redirect']) ? $this->data['redirect'] : false;
return $this->data['redirect'] ?? false;
}
public function onKernelController(FilterControllerEvent $event)
public function getForwardToken()
{
return $this->data['forward_token'] ?? null;
}
public function onKernelController(ControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
public function onKernelResponse(FilterResponseEvent $event)
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest() || !$event->getRequest()->hasSession() || !$event->getRequest()->getSession()->isStarted()) {
if (!$event->isMainRequest()) {
return;
}
if ($event->getRequest()->getSession()->has('sf_redirect')) {
if ($event->getRequest()->cookies->has('sf_redirect')) {
$event->getRequest()->attributes->set('_redirected', true);
}
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
return [
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
);
];
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'request';
}
public function collectSessionUsage(): void
{
$trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
$traceEndIndex = \count($trace) - 1;
for ($i = $traceEndIndex; $i > 0; --$i) {
if (null !== ($class = $trace[$i]['class'] ?? null) && (is_subclass_of($class, SessionInterface::class) || is_subclass_of($class, SessionBagInterface::class))) {
$traceEndIndex = $i;
break;
}
}
if ((\count($trace) - 1) === $traceEndIndex) {
return;
}
// Remove part of the backtrace that belongs to session only
array_splice($trace, 0, $traceEndIndex);
// Merge identical backtraces generated by internal call reports
$name = sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']);
if (!\array_key_exists($name, $this->sessionUsages)) {
$this->sessionUsages[$name] = [
'name' => $name,
'file' => $trace[0]['file'],
'line' => $trace[0]['line'],
'trace' => $trace,
];
}
}
/**
* Parse a controller.
*
* @param mixed $controller The controller to parse
*
* @return array|string An array of controller data or a simple string
*/
protected function parseController($controller)
private function parseController(array|object|string|null $controller): array|string
{
if (is_string($controller) && false !== strpos($controller, '::')) {
if (\is_string($controller) && str_contains($controller, '::')) {
$controller = explode('::', $controller);
}
if (is_array($controller)) {
if (\is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
return array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
return [
'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
];
} catch (\ReflectionException $e) {
if (is_callable($controller)) {
if (\is_callable($controller)) {
// using __call or __callStatic
return array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
return [
'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => 'n/a',
'line' => 'n/a',
);
];
}
}
}
@@ -373,25 +465,38 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
if ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
return array(
$controller = [
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
];
if (str_contains($r->name, '{closure}')) {
return $controller;
}
$controller['method'] = $r->name;
if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
$controller['class'] = $class->name;
} else {
return $r->name;
}
return $controller;
}
if (is_object($controller)) {
if (\is_object($controller)) {
$r = new \ReflectionClass($controller);
return array(
return [
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
];
}
return is_string($controller) ? $controller : 'n/a';
return \is_string($controller) ? $controller : 'n/a';
}
}

View File

@@ -11,35 +11,32 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
/**
* RouterDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterDataCollector extends DataCollector
{
/**
* @var \SplObjectStorage<Request, callable>
*/
protected $controllers;
public function __construct()
{
$this->controllers = new \SplObjectStorage();
$this->data = array(
'redirect' => false,
'url' => null,
'route' => null,
);
$this->reset();
}
/**
* {@inheritdoc}
*
* @final
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
if ($response instanceof RedirectResponse) {
$this->data['redirect'] = true;
@@ -53,17 +50,26 @@ class RouterDataCollector extends DataCollector
unset($this->controllers[$request]);
}
protected function guessRoute(Request $request, $controller)
public function reset()
{
$this->controllers = new \SplObjectStorage();
$this->data = [
'redirect' => false,
'url' => null,
'route' => null,
];
}
protected function guessRoute(Request $request, string|object|array $controller)
{
return 'n/a';
}
/**
* Remembers the controller associated to each request.
*
* @param FilterControllerEvent $event The filter controller event
*/
public function onKernelController(FilterControllerEvent $event)
public function onKernelController(ControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
@@ -71,23 +77,17 @@ class RouterDataCollector extends DataCollector
/**
* @return bool Whether this request will result in a redirect
*/
public function getRedirect()
public function getRedirect(): bool
{
return $this->data['redirect'];
}
/**
* @return string|null The target URL
*/
public function getTargetUrl()
public function getTargetUrl(): ?string
{
return $this->data['url'];
}
/**
* @return string|null The target route
*/
public function getTargetRoute()
public function getTargetRoute(): ?string
{
return $this->data['route'];
}
@@ -95,7 +95,7 @@ class RouterDataCollector extends DataCollector
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'router';
}

View File

@@ -14,18 +14,20 @@ namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
/**
* TimeDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class TimeDataCollector extends DataCollector implements LateDataCollectorInterface
{
protected $kernel;
protected $stopwatch;
private $kernel;
private $stopwatch;
public function __construct(KernelInterface $kernel = null, $stopwatch = null)
public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch = null)
{
$this->kernel = $kernel;
$this->stopwatch = $stopwatch;
@@ -34,7 +36,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
if (null !== $this->kernel) {
$startTime = $this->kernel->getStartTime();
@@ -42,11 +44,24 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
$startTime = $request->server->get('REQUEST_TIME_FLOAT');
}
$this->data = array(
'token' => $response->headers->get('X-Debug-Token'),
$this->data = [
'token' => $request->attributes->get('_stopwatch_token'),
'start_time' => $startTime * 1000,
'events' => array(),
);
'events' => [],
'stopwatch_installed' => class_exists(Stopwatch::class, false),
];
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
if (null !== $this->stopwatch) {
$this->stopwatch->reset();
}
}
/**
@@ -61,9 +76,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
}
/**
* Sets the request events.
*
* @param array $events The request events
* @param StopwatchEvent[] $events The request events
*/
public function setEvents(array $events)
{
@@ -75,21 +88,17 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
}
/**
* Gets the request events.
*
* @return array The request events
* @return StopwatchEvent[]
*/
public function getEvents()
public function getEvents(): array
{
return $this->data['events'];
}
/**
* Gets the request elapsed time.
*
* @return float The elapsed time
*/
public function getDuration()
public function getDuration(): float
{
if (!isset($this->data['events']['__section__'])) {
return 0;
@@ -104,10 +113,8 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
* Gets the initialization time.
*
* This is the time spent until the beginning of the request handling.
*
* @return float The elapsed time
*/
public function getInitTime()
public function getInitTime(): float
{
if (!isset($this->data['events']['__section__'])) {
return 0;
@@ -116,20 +123,20 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
return $this->data['events']['__section__']->getOrigin() - $this->getStartTime();
}
/**
* Gets the request time.
*
* @return int The time
*/
public function getStartTime()
public function getStartTime(): float
{
return $this->data['start_time'];
}
public function isStopwatchInstalled(): bool
{
return $this->data['stopwatch_installed'];
}
/**
* {@inheritdoc}
*/
public function getName()
public function getName(): string
{
return 'time';
}

View File

@@ -1,99 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector\Util;
@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since version 3.2 and will be removed in 4.0. Use the VarDumper component instead.', E_USER_DEPRECATED);
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead.
*/
class ValueExporter
{
/**
* Converts a PHP value to a string.
*
* @param mixed $value The PHP value
* @param int $depth only for internal usage
* @param bool $deep only for internal usage
*
* @return string The string representation of the given value
*/
public function exportValue($value, $depth = 1, $deep = false)
{
if ($value instanceof \__PHP_Incomplete_Class) {
return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value));
}
if (is_object($value)) {
if ($value instanceof \DateTimeInterface) {
return sprintf('Object(%s) - %s', get_class($value), $value->format(\DateTime::ATOM));
}
return sprintf('Object(%s)', get_class($value));
}
if (is_array($value)) {
if (empty($value)) {
return '[]';
}
$indent = str_repeat(' ', $depth);
$a = array();
foreach ($value as $k => $v) {
if (is_array($v)) {
$deep = true;
}
$a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep));
}
if ($deep) {
return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1));
}
$s = sprintf('[%s]', implode(', ', $a));
if (80 > strlen($s)) {
return $s;
}
return sprintf("[\n%s%s\n]", $indent, implode(sprintf(",\n%s", $indent), $a));
}
if (is_resource($value)) {
return sprintf('Resource(%s#%d)', get_resource_type($value), $value);
}
if (null === $value) {
return 'null';
}
if (false === $value) {
return 'false';
}
if (true === $value) {
return 'true';
}
return (string) $value;
}
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
{
$array = new \ArrayObject($value);
return $array['__PHP_Incomplete_Class_Name'];
}
}

View File

@@ -13,25 +13,40 @@ namespace Symfony\Component\HttpKernel\Debug;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Formats debug file links.
*
* @author Jérémy Romey <jeremy@free-agent.fr>
*
* @final
*/
class FileLinkFormatter implements \Serializable
class FileLinkFormatter
{
private $fileLinkFormat;
private $requestStack;
private $baseDir;
private $urlFormat;
private const FORMATS = [
'textmate' => 'txmt://open?url=file://%f&line=%l',
'macvim' => 'mvim://open?url=file://%f&line=%l',
'emacs' => 'emacs://open?url=file://%f&line=%l',
'sublime' => 'subl://open?url=file://%f&line=%l',
'phpstorm' => 'phpstorm://open?file=%f&line=%l',
'atom' => 'atom://core/open/file?filename=%f&line=%l',
'vscode' => 'vscode://file/%f:%l',
];
public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $urlFormat = null)
private array|false $fileLinkFormat;
private $requestStack = null;
private ?string $baseDir = null;
private \Closure|string|null $urlFormat;
/**
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
*/
public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null)
{
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f);
$fileLinkFormat = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE);
if (!\is_array($fileLinkFormat) && $fileLinkFormat = (self::FORMATS[$fileLinkFormat] ?? $fileLinkFormat) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: false) {
$i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
$fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE);
}
$this->fileLinkFormat = $fileLinkFormat;
@@ -40,49 +55,61 @@ class FileLinkFormatter implements \Serializable
$this->urlFormat = $urlFormat;
}
public function format($file, $line)
public function format(string $file, int $line)
{
if ($fmt = $this->getFileLinkFormat()) {
for ($i = 1; isset($fmt[$i]); ++$i) {
if (0 === strpos($file, $k = $fmt[$i++])) {
$file = substr_replace($file, $fmt[$i], 0, strlen($k));
if (str_starts_with($file, $k = $fmt[$i++])) {
$file = substr_replace($file, $fmt[$i], 0, \strlen($k));
break;
}
}
return strtr($fmt[0], array('%f' => $file, '%l' => $line));
return strtr($fmt[0], ['%f' => $file, '%l' => $line]);
}
return false;
}
public function serialize()
/**
* @internal
*/
public function __sleep(): array
{
return serialize($this->getFileLinkFormat());
$this->fileLinkFormat = $this->getFileLinkFormat();
return ['fileLinkFormat'];
}
public function unserialize($serialized)
/**
* @internal
*/
public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string
{
if (\PHP_VERSION_ID >= 70000) {
$this->fileLinkFormat = unserialize($serialized, array('allowed_classes' => false));
} else {
$this->fileLinkFormat = unserialize($serialized);
try {
return $router->generate($routeName).$queryString;
} catch (\Throwable $e) {
return null;
}
}
private function getFileLinkFormat()
private function getFileLinkFormat(): array|false
{
if ($this->fileLinkFormat) {
return $this->fileLinkFormat;
}
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
$request = $this->requestStack->getMasterRequest();
if ($request instanceof Request) {
return array(
$request->getSchemeAndHttpHost().$request->getBaseUrl().$this->urlFormat,
$this->baseDir.DIRECTORY_SEPARATOR, '',
);
$request = $this->requestStack->getMainRequest();
if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) {
return [
$request->getSchemeAndHttpHost().$this->urlFormat,
$this->baseDir.\DIRECTORY_SEPARATOR, '',
];
}
}
return false;
}
}

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\HttpKernel\Debug;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\Event;
/**
* Collects some data about event listeners.
@@ -27,10 +26,11 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
/**
* {@inheritdoc}
*/
protected function preDispatch($eventName, Event $event)
protected function beforeDispatch(string $eventName, object $event)
{
switch ($eventName) {
case KernelEvents::REQUEST:
$event->getRequest()->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6));
$this->stopwatch->openSection();
break;
case KernelEvents::VIEW:
@@ -41,14 +41,17 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
}
break;
case KernelEvents::TERMINATE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
$sectionId = $event->getRequest()->attributes->get('_stopwatch_token');
if (null === $sectionId) {
break;
}
// There is a very special case when using built-in AppCache class as kernel wrapper, in the case
// of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A].
// In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID
// is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception
// which must be caught.
try {
$this->stopwatch->openSection($token);
$this->stopwatch->openSection($sectionId);
} catch (\LogicException $e) {
}
break;
@@ -58,22 +61,28 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
/**
* {@inheritdoc}
*/
protected function postDispatch($eventName, Event $event)
protected function afterDispatch(string $eventName, object $event)
{
switch ($eventName) {
case KernelEvents::CONTROLLER_ARGUMENTS:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
$this->stopwatch->stopSection($token);
$sectionId = $event->getRequest()->attributes->get('_stopwatch_token');
if (null === $sectionId) {
break;
}
$this->stopwatch->stopSection($sectionId);
break;
case KernelEvents::TERMINATE:
// In the special case described in the `preDispatch` method above, the `$token` section
// does not exist, then closing it throws an exception which must be caught.
$token = $event->getResponse()->headers->get('X-Debug-Token');
$sectionId = $event->getRequest()->attributes->get('_stopwatch_token');
if (null === $sectionId) {
break;
}
try {
$this->stopwatch->stopSection($token);
$this->stopwatch->stopSection($sectionId);
} catch (\LogicException $e) {
}
break;

View File

@@ -12,9 +12,10 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\HttpKernel\Kernel;
/**
@@ -36,23 +37,17 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
$classes = array();
$annotatedClasses = array();
$annotatedClasses = [];
foreach ($container->getExtensions() as $extension) {
if ($extension instanceof Extension) {
if (\PHP_VERSION_ID < 70000) {
$classes = array_merge($classes, $extension->getClassesToCompile());
}
$annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile());
$annotatedClasses[] = $extension->getAnnotatedClassesToCompile();
}
}
$annotatedClasses = array_merge($this->kernel->getAnnotatedClassesToCompile(), ...$annotatedClasses);
$existingClasses = $this->getClassesInComposerClassMaps();
if (\PHP_VERSION_ID < 70000) {
$classes = $container->getParameterBag()->resolveValue($classes);
$this->kernel->setClassCache($this->expandClasses($classes, $existingClasses));
}
$annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses);
$this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses));
}
@@ -62,16 +57,14 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface
*
* @param array $patterns The class patterns to expand
* @param array $classes The existing classes to match against the patterns
*
* @return array A list of classes derivated from the patterns
*/
private function expandClasses(array $patterns, array $classes)
private function expandClasses(array $patterns, array $classes): array
{
$expanded = array();
$expanded = [];
// Explicit classes declared in the patterns are returned directly
foreach ($patterns as $key => $pattern) {
if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) {
if (!str_ends_with($pattern, '\\') && !str_contains($pattern, '*')) {
unset($patterns[$key]);
$expanded[] = ltrim($pattern, '\\');
}
@@ -91,20 +84,20 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface
return array_unique($expanded);
}
private function getClassesInComposerClassMaps()
private function getClassesInComposerClassMaps(): array
{
$classes = array();
$classes = [];
foreach (spl_autoload_functions() as $function) {
if (!is_array($function)) {
if (!\is_array($function)) {
continue;
}
if ($function[0] instanceof DebugClassLoader) {
if ($function[0] instanceof DebugClassLoader || $function[0] instanceof LegacyDebugClassLoader) {
$function = $function[0]->getClassLoader();
}
if (is_array($function) && $function[0] instanceof ClassLoader) {
if (\is_array($function) && $function[0] instanceof ClassLoader) {
$classes += array_filter($function[0]->getClassMap());
}
}
@@ -112,19 +105,19 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface
return array_keys($classes);
}
private function patternsToRegexps($patterns)
private function patternsToRegexps(array $patterns): array
{
$regexps = array();
$regexps = [];
foreach ($patterns as $pattern) {
// Escape user input
$regex = preg_quote(ltrim($pattern, '\\'));
// Wildcards * and **
$regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?'));
$regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']);
// If this class does not end by a slash, anchor the end
if (substr($regex, -1) !== '\\') {
if ('\\' !== substr($regex, -1)) {
$regex .= '$';
}
@@ -134,12 +127,12 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface
return $regexps;
}
private function matchAnyRegexps($class, $regexps)
private function matchAnyRegexps(string $class, array $regexps): bool
{
$blacklisted = false !== strpos($class, 'Test');
$isTest = str_contains($class, 'Test');
foreach ($regexps as $regex) {
if ($blacklisted && false === strpos($regex, 'Test')) {
if ($isTest && !str_contains($regex, 'Test')) {
continue;
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since version 3.3 and will be removed in 4.0.', E_USER_DEPRECATED);
/**
* Sets the classes to compile in the cache for the container.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 3.3, to be removed in 4.0.
*/
class AddClassesToCachePass extends AddAnnotatedClassesToCachePass
{
}

View File

@@ -37,9 +37,6 @@ abstract class ConfigurableExtension extends Extension
/**
* Configures the passed container according to the merged configuration.
*
* @param array $mergedConfig
* @param ContainerBuilder $container
*/
abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container);
}

View File

@@ -15,6 +15,9 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Gathers and configures the argument value resolvers.
@@ -25,24 +28,26 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $argumentResolverService;
private $argumentValueResolverTag;
public function __construct($argumentResolverService = 'argument_resolver', $argumentValueResolverTag = 'controller.argument_value_resolver')
{
$this->argumentResolverService = $argumentResolverService;
$this->argumentValueResolverTag = $argumentValueResolverTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->argumentResolverService)) {
if (!$container->hasDefinition('argument_resolver')) {
return;
}
$resolvers = $this->findAndSortTaggedServices('controller.argument_value_resolver', $container);
if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) {
foreach ($resolvers as $resolverReference) {
$id = (string) $resolverReference;
$container->register("debug.$id", TraceableValueResolver::class)
->setDecoratedService($id)
->setArguments([new Reference("debug.$id.inner"), new Reference('debug.stopwatch')]);
}
}
$container
->getDefinition($this->argumentResolverService)
->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container)))
->getDefinition('argument_resolver')
->replaceArgument(1, new IteratorArgument($resolvers))
;
}
}

View File

@@ -20,51 +20,16 @@ use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension;
*/
abstract class Extension extends BaseExtension
{
private $classes = array();
private $annotatedClasses = array();
/**
* Gets the classes to cache.
*
* @return array An array of classes
*
* @deprecated since version 3.3, to be removed in 4.0.
*/
public function getClassesToCompile()
{
if (\PHP_VERSION_ID >= 70000) {
@trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED);
}
return $this->classes;
}
private array $annotatedClasses = [];
/**
* Gets the annotated classes to cache.
*
* @return array An array of classes
*/
public function getAnnotatedClassesToCompile()
public function getAnnotatedClassesToCompile(): array
{
return $this->annotatedClasses;
}
/**
* Adds classes to the class cache.
*
* @param array $classes An array of class patterns
*
* @deprecated since version 3.3, to be removed in 4.0.
*/
public function addClassesToCompile(array $classes)
{
if (\PHP_VERSION_ID >= 70000) {
@trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED);
}
$this->classes = array_merge($this->classes, $classes);
}
/**
* Adds annotated classes to the class cache.
*

View File

@@ -11,9 +11,9 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
@@ -25,28 +25,15 @@ use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
*/
class FragmentRendererPass implements CompilerPassInterface
{
private $handlerService;
private $rendererTag;
/**
* @param string $handlerService Service name of the fragment handler in the container
* @param string $rendererTag Tag name used for fragments
*/
public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer')
{
$this->handlerService = $handlerService;
$this->rendererTag = $rendererTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->handlerService)) {
if (!$container->hasDefinition('fragment.handler')) {
return;
}
$definition = $container->getDefinition($this->handlerService);
$renderers = array();
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) {
$definition = $container->getDefinition('fragment.handler');
$renderers = [];
foreach ($container->findTaggedServiceIds('kernel.fragment_renderer', true) as $id => $tags) {
$def = $container->getDefinition($id);
$class = $container->getParameterBag()->resolveValue($def->getClass());

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\DependencyInjection;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
/**
@@ -23,54 +24,24 @@ use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
class LazyLoadingFragmentHandler extends FragmentHandler
{
private $container;
/**
* @deprecated since version 3.3, to be removed in 4.0
*/
private $rendererIds = array();
private $initialized = array();
/**
* Constructor.
*
* @param ContainerInterface $container A container
* @param RequestStack $requestStack The Request stack that controls the lifecycle of requests
* @param bool $debug Whether the debug mode is enabled or not
* @var array<string, bool>
*/
public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false)
private array $initialized = [];
public function __construct(ContainerInterface $container, RequestStack $requestStack, bool $debug = false)
{
$this->container = $container;
parent::__construct($requestStack, array(), $debug);
}
/**
* Adds a service as a fragment renderer.
*
* @param string $name The service name
* @param string $renderer The render service id
*
* @deprecated since version 3.3, to be removed in 4.0
*/
public function addRendererService($name, $renderer)
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
$this->rendererIds[$name] = $renderer;
parent::__construct($requestStack, [], $debug);
}
/**
* {@inheritdoc}
*/
public function render($uri, $renderer = 'inline', array $options = array())
public function render(string|ControllerReference $uri, string $renderer = 'inline', array $options = []): ?string
{
// BC 3.x, to be removed in 4.0
if (isset($this->rendererIds[$renderer])) {
$this->addRenderer($this->container->get($this->rendererIds[$renderer]));
unset($this->rendererIds[$renderer]);
return parent::render($uri, $renderer, $options);
}
if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) {
$this->addRenderer($this->container->get($renderer));
$this->initialized[$renderer] = true;

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Log\Logger;
/**
* Registers the default logger if necessary.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class LoggerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$container->setAlias(LoggerInterface::class, 'logger')
->setPublic(false);
if ($container->has('logger')) {
return;
}
$container->register('logger', Logger::class)
->setPublic(false);
}
}

View File

@@ -21,8 +21,11 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass
{
private $extensions;
private array $extensions;
/**
* @param string[] $extensions
*/
public function __construct(array $extensions)
{
$this->extensions = $extensions;
@@ -31,8 +34,8 @@ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPas
public function process(ContainerBuilder $container)
{
foreach ($this->extensions as $extension) {
if (!count($container->getExtensionConfig($extension))) {
$container->loadFromExtension($extension, array());
if (!\count($container->getExtensionConfig($extension))) {
$container->loadFromExtension($extension, []);
}
}

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
@@ -22,6 +23,9 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Creates the service-locators required by ServiceValueResolver.
@@ -30,33 +34,34 @@ use Symfony\Component\DependencyInjection\TypedReference;
*/
class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
{
private $resolverServiceId;
private $controllerTag;
public function __construct($resolverServiceId = 'argument_resolver.service', $controllerTag = 'controller.service_arguments')
{
$this->resolverServiceId = $resolverServiceId;
$this->controllerTag = $controllerTag;
}
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition($this->resolverServiceId)) {
if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) {
return;
}
$parameterBag = $container->getParameterBag();
$controllers = array();
$controllers = [];
foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) {
$publicAliases = [];
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPublic() && !$alias->isPrivate()) {
$publicAliases[(string) $alias][] = $id;
}
}
foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) {
$def = $container->getDefinition($id);
$def->setPublic(true);
$class = $def->getClass();
$autowire = $def->isAutowired();
$bindings = $def->getBindings();
// resolve service class, taking parent definitions into account
while (!$class && $def instanceof ChildDefinition) {
while ($def instanceof ChildDefinition) {
$def = $container->findDefinition($def->getParent());
$class = $def->getClass();
$class = $class ?: $def->getClass();
$bindings += $def->getBindings();
}
$class = $parameterBag->resolveValue($class);
@@ -66,14 +71,14 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
$isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class);
// get regular public methods
$methods = array();
$arguments = array();
$methods = [];
$arguments = [];
foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) {
if ('setContainer' === $r->name && $isContainerAware) {
continue;
}
if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) {
$methods[strtolower($r->name)] = array($r, $r->getParameters());
$methods[strtolower($r->name)] = [$r, $r->getParameters()];
}
}
@@ -83,15 +88,15 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
$autowire = true;
continue;
}
foreach (array('action', 'argument', 'id') as $k) {
foreach (['action', 'argument', 'id'] as $k) {
if (!isset($attributes[$k][0])) {
throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, json_encode($attributes, JSON_UNESCAPED_UNICODE), $id));
throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "controller.service_arguments" %s for service "%s".', $k, json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id));
}
}
if (!isset($methods[$action = strtolower($attributes['action'])])) {
throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class));
throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "controller.service_arguments" for service "%s": no public "%s()" method found on class "%s".', $id, $attributes['action'], $class));
}
list($r, $parameters) = $methods[$action];
[$r, $parameters] = $methods[$action];
$found = false;
foreach ($parameters as $p) {
@@ -105,30 +110,55 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
}
if (!$found) {
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class));
throw new InvalidArgumentException(sprintf('Invalid "controller.service_arguments" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $id, $r->name, $attributes['argument'], $class));
}
}
foreach ($methods as list($r, $parameters)) {
foreach ($methods as [$r, $parameters]) {
/** @var \ReflectionMethod $r */
// create a per-method map of argument-names to service/type-references
$args = array();
$args = [];
foreach ($parameters as $p) {
/** @var \ReflectionParameter $p */
$type = $target = ProxyHelper::getTypeHint($r, $p, true);
$type = ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\');
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
if (isset($arguments[$r->name][$p->name])) {
$target = $arguments[$r->name][$p->name];
if ('?' !== $target[0]) {
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE;
} elseif ('' === $target = (string) substr($target, 1)) {
throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id));
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;
}
} elseif (!$type || !$autowire) {
} elseif (isset($bindings[$bindingName = $type.' $'.$name = Target::parseName($p)]) || isset($bindings[$bindingName = '$'.$name]) || isset($bindings[$bindingName = $type])) {
$binding = $bindings[$bindingName];
[$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues();
$binding->setValues([$bindingValue, $bindingId, true, $bindingType, $bindingFile]);
if (!$bindingValue instanceof Reference) {
$args[$p->name] = new Reference('.value.'.$container->hash($bindingValue));
$container->register((string) $args[$p->name], 'mixed')
->setFactory('current')
->addArgument([$bindingValue]);
} else {
$args[$p->name] = $bindingValue;
}
continue;
} elseif (!$type || !$autowire || '\\' !== $target[0]) {
continue;
} elseif (is_subclass_of($type, \UnitEnum::class)) {
// do not attempt to register enum typed arguments if not already present in bindings
continue;
} elseif (!$p->allowsNull()) {
$invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE;
}
if (Request::class === $type || SessionInterface::class === $type || Response::class === $type) {
continue;
}
@@ -140,19 +170,38 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
$message .= ' Did you forget to add a use statement?';
}
throw new InvalidArgumentException($message);
}
$container->register($erroredId = '.errored.'.$container->hash($message), $type)
->addError($message);
$args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior);
$args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE);
} else {
$target = ltrim($target, '\\');
$args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior);
}
}
// 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);
foreach ($publicAliases[$id] ?? [] as $alias) {
$controllers[$alias.'::'.$r->name] = clone $controllers[$id.'::'.$r->name];
}
}
}
}
$container->getDefinition($this->resolverServiceId)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers));
$controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers);
if ($container->hasDefinition('argument_resolver.service')) {
$container->getDefinition('argument_resolver.service')
->replaceArgument(0, $controllerLocatorRef);
}
if ($container->hasDefinition('argument_resolver.not_tagged_controller')) {
$container->getDefinition('argument_resolver.not_tagged_controller')
->replaceArgument(0, $controllerLocatorRef);
}
$container->setAlias('argument_resolver.controller_locator', (string) $controllerLocatorRef);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Register all services that have the "kernel.locale_aware" tag into the listener.
*
* @author Pierre Bobiet <pierrebobiet@gmail.com>
*/
class RegisterLocaleAwareServicesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('locale_aware_listener')) {
return;
}
$services = [];
foreach ($container->findTaggedServiceIds('kernel.locale_aware') as $id => $tags) {
$services[] = new Reference($id);
}
if (!$services) {
$container->removeDefinition('locale_aware_listener');
return;
}
$container
->getDefinition('locale_aware_listener')
->setArgument(0, new IteratorArgument($services))
;
}
}

View File

@@ -21,21 +21,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface
{
private $resolverServiceId;
public function __construct($resolverServiceId = 'argument_resolver.service')
{
$this->resolverServiceId = $resolverServiceId;
}
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition($this->resolverServiceId)) {
return;
}
$serviceResolver = $container->getDefinition($this->resolverServiceId);
$controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0));
$controllerLocator = $container->findDefinition('argument_resolver.controller_locator');
$controllers = $controllerLocator->getArgument(0);
foreach ($controllers as $controller => $argumentRef) {
@@ -47,19 +35,23 @@ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface
} else {
// any methods listed for call-at-instantiation cannot be actions
$reason = false;
$action = substr(strrchr($controller, ':'), 1);
$id = substr($controller, 0, -1 - strlen($action));
[$id, $action] = explode('::', $controller);
if ($container->hasAlias($id)) {
continue;
}
$controllerDef = $container->getDefinition($id);
foreach ($controllerDef->getMethodCalls() as list($method, $args)) {
foreach ($controllerDef->getMethodCalls() as [$method]) {
if (0 === strcasecmp($action, $method)) {
$reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id);
break;
}
}
if (!$reason) {
if ($controllerDef->getClass() === $id) {
$controllers[$id.'::'.$action] = $argumentRef;
}
// see Symfony\Component\HttpKernel\Controller\ContainerControllerResolver
$controllers[$id.':'.$action] = $argumentRef;
if ('__invoke' === $action) {
$controllers[$id] = $argumentRef;
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Alexander M. Turek <me@derrabus.de>
*/
class ResettableServicePass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has('services_resetter')) {
return;
}
$services = $methods = [];
foreach ($container->findTaggedServiceIds('kernel.reset', true) as $id => $tags) {
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
foreach ($tags as $attributes) {
if (!isset($attributes['method'])) {
throw new RuntimeException(sprintf('Tag "kernel.reset" requires the "method" attribute to be set on service "%s".', $id));
}
if (!isset($methods[$id])) {
$methods[$id] = [];
}
if ('ignore' === ($attributes['on_invalid'] ?? null)) {
$attributes['method'] = '?'.$attributes['method'];
}
$methods[$id][] = $attributes['method'];
}
}
if (!$services) {
$container->removeAlias('services_resetter');
$container->removeDefinition('services_resetter');
return;
}
$container->findDefinition('services_resetter')
->setArgument(0, new IteratorArgument($services))
->setArgument(1, $methods);
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Contracts\Service\ResetInterface;
/**
* Resets provided services.
*
* @author Alexander M. Turek <me@derrabus.de>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ServicesResetter implements ResetInterface
{
private \Traversable $resettableServices;
private array $resetMethods;
/**
* @param \Traversable<string, object> $resettableServices
* @param array<string, string|string[]> $resetMethods
*/
public function __construct(\Traversable $resettableServices, array $resetMethods)
{
$this->resettableServices = $resettableServices;
$this->resetMethods = $resetMethods;
}
public function reset()
{
foreach ($this->resettableServices as $id => $service) {
foreach ((array) $this->resetMethods[$id] as $resetMethod) {
if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) {
continue;
}
$service->$resetMethod();
}
}
}
}

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows filtering of controller arguments.
@@ -26,28 +26,34 @@ use Symfony\Component\HttpFoundation\Request;
*
* @author Christophe Coevoet <stof@notk.org>
*/
class FilterControllerArgumentsEvent extends FilterControllerEvent
final class ControllerArgumentsEvent extends KernelEvent
{
private $arguments;
private $controller;
private array $arguments;
public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType)
public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, ?int $requestType)
{
parent::__construct($kernel, $controller, $request, $requestType);
parent::__construct($kernel, $request, $requestType);
$this->controller = $controller;
$this->arguments = $arguments;
}
/**
* @return array
*/
public function getArguments()
public function getController(): callable
{
return $this->controller;
}
public function setController(callable $controller)
{
$this->controller = $controller;
}
public function getArguments(): array
{
return $this->arguments;
}
/**
* @param array $arguments
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows filtering of a controller callable.
@@ -25,36 +25,23 @@ use Symfony\Component\HttpFoundation\Request;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FilterControllerEvent extends KernelEvent
final class ControllerEvent extends KernelEvent
{
/**
* The current controller.
*/
private $controller;
private string|array|object $controller;
public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType)
public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType)
{
parent::__construct($kernel, $request, $requestType);
$this->setController($controller);
}
/**
* Returns the current controller.
*
* @return callable
*/
public function getController()
public function getController(): callable
{
return $this->controller;
}
/**
* Sets a new controller.
*
* @param callable $controller
*/
public function setController(callable $controller)
public function setController(callable $controller): void
{
$this->controller = $controller;
}

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to create a response for a thrown exception.
@@ -21,69 +21,51 @@ use Symfony\Component\HttpFoundation\Request;
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* You can also call setException() to replace the thrown exception. This
* You can also call setThrowable() to replace the thrown exception. This
* exception will be thrown if no response is set during processing of this
* event.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseForExceptionEvent extends GetResponseEvent
final class ExceptionEvent extends RequestEvent
{
/**
* The exception object.
*
* @var \Exception
*/
private $exception;
private \Throwable $throwable;
private bool $allowCustomResponseCode = false;
/**
* @var bool
*/
private $allowCustomResponseCode = false;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e)
public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, \Throwable $e)
{
parent::__construct($kernel, $request, $requestType);
$this->setException($e);
$this->setThrowable($e);
}
/**
* Returns the thrown exception.
*
* @return \Exception The thrown exception
*/
public function getException()
public function getThrowable(): \Throwable
{
return $this->exception;
return $this->throwable;
}
/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*
* @param \Exception $exception The thrown exception
*/
public function setException(\Exception $exception)
public function setThrowable(\Throwable $exception): void
{
$this->exception = $exception;
$this->throwable = $exception;
}
/**
* Mark the event as allowing a custom response code.
*/
public function allowCustomResponseCode()
public function allowCustomResponseCode(): void
{
$this->allowCustomResponseCode = true;
}
/**
* Returns true if the event allows a custom response code.
*
* @return bool
*/
public function isAllowingCustomResponseCode()
public function isAllowingCustomResponseCode(): bool
{
return $this->allowCustomResponseCode;
}

View File

@@ -16,6 +16,6 @@ namespace Symfony\Component\HttpKernel\Event;
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class FinishRequestEvent extends KernelEvent
final class FinishRequestEvent extends KernelEvent
{
}

View File

@@ -11,9 +11,9 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Base class for events thrown in the HttpKernel component.
@@ -22,29 +22,15 @@ use Symfony\Component\EventDispatcher\Event;
*/
class KernelEvent extends Event
{
/**
* The kernel in which this event was thrown.
*
* @var HttpKernelInterface
*/
private $kernel;
/**
* The request the kernel is currently processing.
*
* @var Request
*/
private $request;
private ?int $requestType;
/**
* The request type the kernel is currently processing. One of
* HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST.
*
* @var int
* @param int $requestType The request type the kernel is currently processing; one of
* HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST
*/
private $requestType;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType)
public function __construct(HttpKernelInterface $kernel, Request $request, ?int $requestType)
{
$this->kernel = $kernel;
$this->request = $request;
@@ -53,20 +39,16 @@ class KernelEvent extends Event
/**
* Returns the kernel in which this event was thrown.
*
* @return HttpKernelInterface
*/
public function getKernel()
public function getKernel(): HttpKernelInterface
{
return $this->kernel;
}
/**
* Returns the request the kernel is currently processing.
*
* @return Request
*/
public function getRequest()
public function getRequest(): Request
{
return $this->request;
}
@@ -74,21 +56,19 @@ class KernelEvent extends Event
/**
* Returns the request type the kernel is currently processing.
*
* @return int One of HttpKernelInterface::MASTER_REQUEST and
* @return int One of HttpKernelInterface::MAIN_REQUEST and
* HttpKernelInterface::SUB_REQUEST
*/
public function getRequestType()
public function getRequestType(): int
{
return $this->requestType;
}
/**
* Checks if this is a master request.
*
* @return bool True if the request is a master request
* Checks if this is the main request.
*/
public function isMasterRequest()
public function isMainRequest(): bool
{
return HttpKernelInterface::MASTER_REQUEST === $this->requestType;
return HttpKernelInterface::MAIN_REQUEST === $this->requestType;
}
}

View File

@@ -22,29 +22,20 @@ use Symfony\Component\HttpFoundation\Response;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseEvent extends KernelEvent
class RequestEvent extends KernelEvent
{
/**
* The response object.
*
* @var Response
*/
private $response;
private $response = null;
/**
* Returns the response object.
*
* @return Response
*/
public function getResponse()
public function getResponse(): ?Response
{
return $this->response;
}
/**
* Sets a response and stops event propagation.
*
* @param Response $response
*/
public function setResponse(Response $response)
{
@@ -55,10 +46,8 @@ class GetResponseEvent extends KernelEvent
/**
* Returns whether a response was set.
*
* @return bool Whether a response was set
*/
public function hasResponse()
public function hasResponse(): bool
{
return null !== $this->response;
}

View File

@@ -11,9 +11,9 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to filter a Response object.
@@ -24,38 +24,23 @@ use Symfony\Component\HttpFoundation\Response;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FilterResponseEvent extends KernelEvent
final class ResponseEvent extends KernelEvent
{
/**
* The current response object.
*
* @var Response
*/
private $response;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response)
public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, Response $response)
{
parent::__construct($kernel, $request, $requestType);
$this->setResponse($response);
}
/**
* Returns the current response object.
*
* @return Response
*/
public function getResponse()
public function getResponse(): Response
{
return $this->response;
}
/**
* Sets a new response object.
*
* @param Response $response
*/
public function setResponse(Response $response)
public function setResponse(Response $response): void
{
$this->response = $response;
}

View File

@@ -11,35 +11,30 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to execute logic after a response was sent.
*
* Since it's only triggered on master requests, the `getRequestType()` method
* will always return the value of `HttpKernelInterface::MASTER_REQUEST`.
* Since it's only triggered on main requests, the `getRequestType()` method
* will always return the value of `HttpKernelInterface::MAIN_REQUEST`.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PostResponseEvent extends KernelEvent
final class TerminateEvent extends KernelEvent
{
private $response;
public function __construct(HttpKernelInterface $kernel, Request $request, Response $response)
{
parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST);
$this->response = $response;
}
/**
* Returns the response for which this event was thrown.
*
* @return Response
*/
public function getResponse()
public function getResponse(): Response
{
return $this->response;
}

View File

@@ -11,8 +11,8 @@
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to create a response for the return value of a controller.
@@ -23,38 +23,23 @@ use Symfony\Component\HttpFoundation\Request;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseForControllerResultEvent extends GetResponseEvent
final class ViewEvent extends RequestEvent
{
/**
* The return value of the controller.
*
* @var mixed
*/
private $controllerResult;
private mixed $controllerResult;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult)
public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, mixed $controllerResult)
{
parent::__construct($kernel, $request, $requestType);
$this->controllerResult = $controllerResult;
}
/**
* Returns the return value of the controller.
*
* @return mixed The controller return value
*/
public function getControllerResult()
public function getControllerResult(): mixed
{
return $this->controllerResult;
}
/**
* Assigns the return value of the controller.
*
* @param mixed $controllerResult The controller return value
*/
public function setControllerResult($controllerResult)
public function setControllerResult(mixed $controllerResult): void
{
$this->controllerResult = $controllerResult;
}

View File

@@ -11,44 +11,290 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\SessionUtils;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Service\ResetInterface;
/**
* Sets the session in the request.
* Sets the session onto the request on the "kernel.request" event and saves
* it on the "kernel.response" event.
*
* In addition, if the session has been started it overrides the Cache-Control
* header in such a way that all caching is disabled in that case.
* If you have a scenario where caching responses with session information in
* them makes sense, you can disable this behaviour by setting the header
* AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*
* @internal
*/
abstract class AbstractSessionListener implements EventSubscriberInterface
abstract class AbstractSessionListener implements EventSubscriberInterface, ResetInterface
{
public function onKernelRequest(GetResponseEvent $event)
public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl';
protected $container;
private bool $debug;
/**
* @var array<string, mixed>
*/
private $sessionOptions;
public function __construct(ContainerInterface $container = null, bool $debug = false, array $sessionOptions = [])
{
if (!$event->isMasterRequest()) {
$this->container = $container;
$this->debug = $debug;
$this->sessionOptions = $sessionOptions;
}
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$session = $this->getSession();
if (null === $session || $request->hasSession()) {
if (!$request->hasSession()) {
// This variable prevents calling `$this->getSession()` twice in case the Request (and the below factory) is cloned
$sess = null;
$request->setSessionFactory(function () use (&$sess, $request) {
if (!$sess) {
$sess = $this->getSession();
/*
* For supporting sessions in php runtime with runners like roadrunner or swoole, the session
* cookie needs to be read from the cookie bag and set on the session storage.
*
* Do not set it when a native php session is active.
*/
if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) {
$sessionId = $sess->getId() ?: $request->cookies->get($sess->getName(), '');
$sess->setId($sessionId);
}
}
return $sess;
});
}
}
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) {
return;
}
$request->setSession($session);
$response = $event->getResponse();
$autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER);
// Always remove the internal header if present
$response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER);
if (!$event->getRequest()->hasSession(true)) {
return;
}
$session = $event->getRequest()->getSession();
if ($session->isStarted()) {
/*
* Saves the session, in case it is still open, before sending the response/headers.
*
* This ensures several things in case the developer did not save the session explicitly:
*
* * If a session save handler without locking is used, it ensures the data is available
* on the next request, e.g. after a redirect. PHPs auto-save at script end via
* session_register_shutdown is executed after fastcgi_finish_request. So in this case
* the data could be missing the next request because it might not be saved the moment
* the new request is processed.
* * A locking save handler (e.g. the native 'files') circumvents concurrency problems like
* the one above. But by saving the session before long-running things in the terminate event,
* we ensure the session is not blocked longer than needed.
* * When regenerating the session ID no locking is involved in PHPs session design. See
* https://bugs.php.net/61470 for a discussion. So in this case, the session must
* be saved anyway before sending the headers with the new session ID. Otherwise session
* data could get lost again for concurrent requests with the new ID. One result could be
* that you get logged out after just logging in.
*
* This listener should be executed as one of the last listeners, so that previous listeners
* can still operate on the open session. This prevents the overhead of restarting it.
* Listeners after closing the session can still work with the session as usual because
* Symfonys session implementation starts the session on demand. So writing to it after
* it is saved will just restart it.
*/
$session->save();
/*
* For supporting sessions in php runtime with runners like roadrunner or swoole the session
* cookie need to be written on the response object and should not be written by PHP itself.
*/
$sessionName = $session->getName();
$sessionId = $session->getId();
$sessionOptions = $this->getSessionOptions($this->sessionOptions);
$sessionCookiePath = $sessionOptions['cookie_path'] ?? '/';
$sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null;
$sessionCookieSecure = $sessionOptions['cookie_secure'] ?? false;
$sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? true;
$sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX;
$sessionUseCookies = $sessionOptions['use_cookies'] ?? true;
SessionUtils::popSessionCookie($sessionName, $sessionId);
if ($sessionUseCookies) {
$request = $event->getRequest();
$requestSessionCookieId = $request->cookies->get($sessionName);
$isSessionEmpty = ($session instanceof Session ? $session->isEmpty() : empty($session->all())) && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions
if ($requestSessionCookieId && $isSessionEmpty) {
// PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument
// which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy
// when the session gets invalidated (for example on logout) so we must handle this case here too
// otherwise we would send two Set-Cookie headers back with the response
SessionUtils::popSessionCookie($sessionName, 'deleted');
$response->headers->clearCookie(
$sessionName,
$sessionCookiePath,
$sessionCookieDomain,
$sessionCookieSecure,
$sessionCookieHttpOnly,
$sessionCookieSameSite
);
} elseif ($sessionId !== $requestSessionCookieId && !$isSessionEmpty) {
$expire = 0;
$lifetime = $sessionOptions['cookie_lifetime'] ?? null;
if ($lifetime) {
$expire = time() + $lifetime;
}
$response->headers->setCookie(
Cookie::create(
$sessionName,
$sessionId,
$expire,
$sessionCookiePath,
$sessionCookieDomain,
$sessionCookieSecure,
$sessionCookieHttpOnly,
false,
$sessionCookieSameSite
)
);
}
}
}
if ($session instanceof Session ? 0 === $session->getUsageIndex() : !$session->isStarted()) {
return;
}
if ($autoCacheControl) {
$maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge();
$response
->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds'))
->setPrivate()
->setMaxAge($maxAge)
->headers->addCacheControlDirective('must-revalidate');
}
if (!$event->getRequest()->attributes->get('_stateless', false)) {
return;
}
if ($this->debug) {
throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
}
if ($this->container->has('logger')) {
$this->container->get('logger')->warning('Session was used while the request was declared stateless.');
}
}
public static function getSubscribedEvents()
public function onSessionUsage(): void
{
return array(
KernelEvents::REQUEST => array('onKernelRequest', 128),
);
if (!$this->debug) {
return;
}
if ($this->container && $this->container->has('session_collector')) {
$this->container->get('session_collector')();
}
if (!$requestStack = $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null) {
return;
}
$stateless = false;
$clonedRequestStack = clone $requestStack;
while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) {
$stateless = $request->attributes->get('_stateless');
}
if (!$stateless) {
return;
}
if (!$session = $requestStack->getCurrentRequest()->getSession()) {
return;
}
if ($session->isStarted()) {
$session->save();
}
throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 128],
// low priority to come after regular response listeners, but higher than StreamedResponseListener
KernelEvents::RESPONSE => ['onKernelResponse', -1000],
];
}
public function reset(): void
{
if (\PHP_SESSION_ACTIVE === session_status()) {
session_abort();
}
session_unset();
$_SESSION = [];
if (!headers_sent()) { // session id can only be reset when no headers were so we check for headers_sent first
session_id('');
}
}
/**
* Gets the session object.
*
* @return SessionInterface|null A SessionInterface instance or null if no session is available
*/
abstract protected function getSession();
abstract protected function getSession(): ?SessionInterface;
private function getSessionOptions(array $sessionOptions): array
{
$mergedSessionOptions = [];
foreach (session_get_cookie_params() as $key => $value) {
$mergedSessionOptions['cookie_'.$key] = $value;
}
foreach ($sessionOptions as $key => $value) {
// do the same logic as in the NativeSessionStorage
if ('cookie_secure' === $key && 'auto' === $value) {
continue;
}
$mergedSessionOptions[$key] = $value;
}
return $mergedSessionOptions;
}
}

View File

@@ -1,84 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* TestSessionListener.
*
* Saves session in test environment.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractTestSessionListener implements EventSubscriberInterface
{
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
// bootstrap the session
$session = $this->getSession();
if (!$session) {
return;
}
$cookies = $event->getRequest()->cookies;
if ($cookies->has($session->getName())) {
$session->setId($cookies->get($session->getName()));
}
}
/**
* Checks if session was initialized and saves if current request is master
* Runs on 'kernel.response' in test environment.
*
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$session = $event->getRequest()->getSession();
if ($session && $session->isStarted()) {
$session->save();
$params = session_get_cookie_params();
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array('onKernelRequest', 192),
KernelEvents::RESPONSE => array('onKernelResponse', -128),
);
}
/**
* Gets the session object.
*
* @return SessionInterface|null A SessionInterface instance or null if no session is available
*/
abstract protected function getSession();
}

View File

@@ -12,24 +12,20 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* Adds configured formats to each request.
*
* @author Gildas Quemener <gildas.quemener@gmail.com>
*
* @final
*/
class AddRequestFormatsListener implements EventSubscriberInterface
{
/**
* @var array
*/
protected $formats;
private array $formats;
/**
* @param array $formats
*/
public function __construct(array $formats)
{
$this->formats = $formats;
@@ -37,21 +33,20 @@ class AddRequestFormatsListener implements EventSubscriberInterface
/**
* Adds request formats.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
foreach ($this->formats as $format => $mimeTypes) {
$event->getRequest()->setFormat($format, $mimeTypes);
$request->setFormat($format, $mimeTypes);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(KernelEvents::REQUEST => array('onKernelRequest', 1));
return [KernelEvents::REQUEST => ['onKernelRequest', 100]];
}
}

View File

@@ -12,133 +12,171 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Configures errors and exceptions handlers.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*
* @internal
*/
class DebugHandlersListener implements EventSubscriberInterface
{
private $exceptionHandler;
private string|object|null $earlyHandler;
private ?\Closure $exceptionHandler;
private $logger;
private $levels;
private $throwAt;
private $scream;
private $fileLinkFormat;
private $scope;
private $firstCall = true;
private $deprecationLogger;
private array|int|null $levels;
private ?int $throwAt;
private bool $scream;
private bool $scope;
private bool $firstCall = true;
private bool $hasTerminatedWithException = false;
/**
* @param callable|null $exceptionHandler A handler that will be called on Exception
* @param LoggerInterface|null $logger A PSR-3 logger
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
* @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
* @param string|array $fileLinkFormat The format for links to source files
* @param bool $scope Enables/disables scoping mode
* @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception
* @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
* @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
* @param bool $scope Enables/disables scoping mode
*/
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true)
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null)
{
$this->exceptionHandler = $exceptionHandler;
$handler = set_exception_handler('is_int');
$this->earlyHandler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler();
$this->exceptionHandler = null === $exceptionHandler || $exceptionHandler instanceof \Closure ? $exceptionHandler : \Closure::fromCallable($exceptionHandler);
$this->logger = $logger;
$this->levels = null === $levels ? E_ALL : $levels;
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? E_ALL : null));
$this->scream = (bool) $scream;
$this->fileLinkFormat = $fileLinkFormat;
$this->scope = (bool) $scope;
$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;
}
/**
* Configures the error handler.
*
* @param Event|null $event The triggering event
*/
public function configure(Event $event = null)
public function configure(object $event = null)
{
if (!$this->firstCall) {
if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
return;
}
$this->firstCall = false;
if ($this->logger || null !== $this->throwAt) {
$handler = set_error_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler instanceof ErrorHandler) {
if ($this->logger) {
$handler->setDefaultLogger($this->logger, $this->levels);
if (is_array($this->levels)) {
$levels = 0;
foreach ($this->levels as $type => $log) {
$levels |= $type;
}
} else {
$levels = $this->levels;
if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) {
return;
}
$this->firstCall = $this->hasTerminatedWithException = false;
$handler = set_exception_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler();
if (!$handler instanceof ErrorHandler) {
$handler = $this->earlyHandler;
}
if ($handler instanceof ErrorHandler) {
if ($this->logger || $this->deprecationLogger) {
$this->setDefaultLoggers($handler);
if (\is_array($this->levels)) {
$levels = 0;
foreach ($this->levels as $type => $log) {
$levels |= $type;
}
if ($this->scream) {
$handler->screamAt($levels);
}
if ($this->scope) {
$handler->scopeAt($this->levels);
} else {
$handler->scopeAt(0, true);
}
$this->logger = $this->levels = null;
} else {
$levels = $this->levels;
}
if (null !== $this->throwAt) {
$handler->throwAt($this->throwAt, true);
if ($this->scream) {
$handler->screamAt($levels);
}
if ($this->scope) {
$handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED);
} else {
$handler->scopeAt(0, true);
}
$this->logger = $this->deprecationLogger = $this->levels = null;
}
if (null !== $this->throwAt) {
$handler->throwAt($this->throwAt, true);
}
}
if (!$this->exceptionHandler) {
if ($event instanceof KernelEvent) {
if (method_exists($event->getKernel(), 'terminateWithException')) {
$this->exceptionHandler = array($event->getKernel(), 'terminateWithException');
if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
$request = $event->getRequest();
$hasRun = &$this->hasTerminatedWithException;
$this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
if ($hasRun) {
throw $e;
}
$hasRun = true;
$kernel->terminateWithException($e, $request);
};
}
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
$output = $event->getOutput();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->exceptionHandler = function ($e) use ($app, $output) {
$app->renderException($e, $output);
$this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
$app->renderThrowable($e, $output);
};
}
}
if ($this->exceptionHandler) {
$handler = set_exception_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
restore_exception_handler();
if ($handler instanceof ErrorHandler) {
$h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler;
$handler->setExceptionHandler($h);
$handler = is_array($h) ? $h[0] : null;
}
if ($handler instanceof ExceptionHandler) {
$handler->setHandler($this->exceptionHandler);
if (null !== $this->fileLinkFormat) {
$handler->setFileLinkFormat($this->fileLinkFormat);
}
$handler->setExceptionHandler($this->exceptionHandler);
}
$this->exceptionHandler = null;
}
}
public static function getSubscribedEvents()
private function setDefaultLoggers(ErrorHandler $handler): void
{
$events = array(KernelEvents::REQUEST => array('configure', 2048));
if (\is_array($this->levels)) {
$levelsDeprecatedOnly = [];
$levelsWithoutDeprecated = [];
foreach ($this->levels as $type => $log) {
if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) {
$levelsDeprecatedOnly[$type] = $log;
} else {
$levelsWithoutDeprecated[$type] = $log;
}
}
} else {
$levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED);
$levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED;
}
if ('cli' === PHP_SAPI && defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = array('configure', 2048);
$defaultLoggerLevels = $this->levels;
if ($this->deprecationLogger && $levelsDeprecatedOnly) {
$handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly);
$defaultLoggerLevels = $levelsWithoutDeprecated;
}
if ($this->logger && $defaultLoggerLevels) {
$handler->setDefaultLogger($this->logger, $defaultLoggerLevels);
}
}
public static function getSubscribedEvents(): array
{
$events = [KernelEvents::REQUEST => ['configure', 2048]];
if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
}
return $events;

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Ensures that the application is not indexed by search engines.
*
* @author Gary PEGEOT <garypegeot@gmail.com>
*/
class DisallowRobotsIndexingListener implements EventSubscriberInterface
{
private const HEADER_NAME = 'X-Robots-Tag';
public function onResponse(ResponseEvent $event): void
{
if (!$event->getResponse()->headers->has(static::HEADER_NAME)) {
$event->getResponse()->headers->set(static::HEADER_NAME, 'noindex');
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['onResponse', -255],
];
}
}

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\Server\Connection;
use Symfony\Component\VarDumper\VarDumper;
/**
@@ -26,34 +27,37 @@ class DumpListener implements EventSubscriberInterface
{
private $cloner;
private $dumper;
private $connection;
/**
* @param ClonerInterface $cloner Cloner service
* @param DataDumperInterface $dumper Dumper service
*/
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper)
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, Connection $connection = null)
{
$this->cloner = $cloner;
$this->dumper = $dumper;
$this->connection = $connection;
}
public function configure()
{
$cloner = $this->cloner;
$dumper = $this->dumper;
$connection = $this->connection;
VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var));
VarDumper::setHandler(static function ($var) use ($cloner, $dumper, $connection) {
$data = $cloner->cloneVar($var);
if (!$connection || !$connection->write($data)) {
$dumper->dump($data);
}
});
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
if (!class_exists(ConsoleEvents::class)) {
return array();
return [];
}
// Register early to have a working dump() as early as possible
return array(ConsoleEvents::COMMAND => array('configure', 1024));
return [ConsoleEvents::COMMAND => ['configure', 1024]];
}
}

View File

@@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class ErrorListener implements EventSubscriberInterface
{
protected $controller;
protected $logger;
protected $debug;
/**
* @var array<class-string, array{log_level: string|null, status_code: int<100,599>|null}>
*/
protected $exceptionsMapping;
/**
* @param array<class-string, array{log_level: string|null, status_code: int<100,599>|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 logKernelException(ExceptionEvent $event)
{
$throwable = $event->getThrowable();
$logLevel = null;
foreach ($this->exceptionsMapping as $class => $config) {
if ($throwable instanceof $class && $config['log_level']) {
$logLevel = $config['log_level'];
break;
}
}
foreach ($this->exceptionsMapping as $class => $config) {
if (!$throwable instanceof $class || !$config['status_code']) {
continue;
}
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);
$event->setThrowable($throwable);
}
break;
}
$e = FlattenException::createFromThrowable($throwable);
$this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel);
}
public function onKernelException(ExceptionEvent $event)
{
if (null === $this->controller) {
return;
}
$throwable = $event->getThrowable();
$request = $this->duplicateRequest($throwable, $event->getRequest());
try {
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
$f = FlattenException::createFromThrowable($e);
$this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine()));
$prev = $e;
do {
if ($throwable === $wrapper = $prev) {
throw $e;
}
} while ($prev = $wrapper->getPrevious());
$prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
$prev->setAccessible(true);
$prev->setValue($wrapper, $throwable);
throw $e;
}
$event->setResponse($response);
if ($this->debug) {
$event->getRequest()->attributes->set('_remove_csp_headers', true);
}
}
public function removeCspHeader(ResponseEvent $event): void
{
if ($this->debug && $event->getRequest()->attributes->get('_remove_csp_headers', false)) {
$event->getResponse()->headers->remove('Content-Security-Policy');
}
}
public function onControllerArguments(ControllerArgumentsEvent $event)
{
$e = $event->getRequest()->attributes->get('exception');
if (!$e instanceof \Throwable || false === $k = array_search($e, $event->getArguments(), true)) {
return;
}
$r = new \ReflectionFunction(\Closure::fromCallable($event->getController()));
$r = $r->getParameters()[$k] ?? null;
if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], true))) {
$arguments = $event->getArguments();
$arguments[$k] = FlattenException::createFromThrowable($e);
$event->setArguments($arguments);
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments',
KernelEvents::EXCEPTION => [
['logKernelException', 0],
['onKernelException', -128],
],
KernelEvents::RESPONSE => ['removeCspHeader', -128],
];
}
/**
* Logs an exception.
*/
protected function logException(\Throwable $exception, string $message, string $logLevel = null): void
{
if (null !== $this->logger) {
if (null !== $logLevel) {
$this->logger->log($logLevel, $message, ['exception' => $exception]);
} elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, ['exception' => $exception]);
} else {
$this->logger->error($message, ['exception' => $exception]);
}
}
}
/**
* Clones the request for the exception.
*/
protected function duplicateRequest(\Throwable $exception, Request $request): Request
{
$attributes = [
'_controller' => $this->controller,
'exception' => $exception,
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
];
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
return $request;
}
}

View File

@@ -1,116 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* ExceptionListener.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExceptionListener implements EventSubscriberInterface
{
protected $controller;
protected $logger;
public function __construct($controller, LoggerInterface $logger = null)
{
$this->controller = $controller;
$this->logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$request = $event->getRequest();
$this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
$request = $this->duplicateRequest($exception, $request);
try {
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
$this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
$wrapper = $e;
while ($prev = $wrapper->getPrevious()) {
if ($exception === $wrapper = $prev) {
throw $e;
}
}
$prev = new \ReflectionProperty('Exception', 'previous');
$prev->setAccessible(true);
$prev->setValue($wrapper, $exception);
throw $e;
}
$event->setResponse($response);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', -128),
);
}
/**
* Logs an exception.
*
* @param \Exception $exception The \Exception instance
* @param string $message The error message to log
*/
protected function logException(\Exception $exception, $message)
{
if (null !== $this->logger) {
if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, array('exception' => $exception));
} else {
$this->logger->error($message, array('exception' => $exception));
}
}
}
/**
* Clones the request for the exception.
*
* @param \Exception $exception The thrown exception
* @param Request $request The original request
*
* @return Request $request The cloned request
*/
protected function duplicateRequest(\Exception $exception, Request $request)
{
$attributes = array(
'_controller' => $this->controller,
'exception' => FlattenException::create($exception),
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
);
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
return $request;
}
}

View File

@@ -11,12 +11,12 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\UriSigner;
/**
* Handles content fragments represented by special URIs.
@@ -24,23 +24,22 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
* All URL paths starting with /_fragment are handled as
* content fragments by this listener.
*
* If throws an AccessDeniedHttpException exception if the request
* Throws an AccessDeniedHttpException exception if the request
* is not signed or if it is not an internal sub-request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class FragmentListener implements EventSubscriberInterface
{
private $signer;
private $fragmentPath;
private string $fragmentPath;
/**
* Constructor.
*
* @param UriSigner $signer A UriSigner instance
* @param string $fragmentPath The path that triggers this listener
* @param string $fragmentPath The path that triggers this listener
*/
public function __construct(UriSigner $signer, $fragmentPath = '/_fragment')
public function __construct(UriSigner $signer, string $fragmentPath = '/_fragment')
{
$this->signer = $signer;
$this->fragmentPath = $fragmentPath;
@@ -49,11 +48,9 @@ class FragmentListener implements EventSubscriberInterface
/**
* Fixes request attributes when the path is '/_fragment'.
*
* @param GetResponseEvent $event A GetResponseEvent instance
*
* @throws AccessDeniedHttpException if the request does not come from a trusted IP.
* @throws AccessDeniedHttpException if the request does not come from a trusted IP
*/
public function onKernelRequest(GetResponseEvent $event)
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
@@ -68,36 +65,35 @@ class FragmentListener implements EventSubscriberInterface
return;
}
if ($event->isMasterRequest()) {
if ($event->isMainRequest()) {
$this->validateRequest($request);
}
parse_str($request->query->get('_path', ''), $attributes);
$request->attributes->add($attributes);
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes));
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes));
$request->query->remove('_path');
}
protected function validateRequest(Request $request)
{
// is the Request safe?
if (!$request->isMethodSafe(false)) {
if (!$request->isMethodSafe()) {
throw new AccessDeniedHttpException();
}
// is the Request signed?
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) {
if ($this->signer->checkRequest($request)) {
return;
}
throw new AccessDeniedHttpException();
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 48)),
);
return [
KernelEvents::REQUEST => [['onKernelRequest', 48]],
];
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Translation\LocaleAwareInterface;
/**
* Pass the current locale to the provided services.
*
* @author Pierre Bobiet <pierrebobiet@gmail.com>
*/
class LocaleAwareListener implements EventSubscriberInterface
{
private iterable $localeAwareServices;
private $requestStack;
/**
* @param iterable<mixed, LocaleAwareInterface> $localeAwareServices
*/
public function __construct(iterable $localeAwareServices, RequestStack $requestStack)
{
$this->localeAwareServices = $localeAwareServices;
$this->requestStack = $requestStack;
}
public function onKernelRequest(RequestEvent $event): void
{
$this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale());
}
public function onKernelFinishRequest(FinishRequestEvent $event): void
{
if (null === $parentRequest = $this->requestStack->getParentRequest()) {
foreach ($this->localeAwareServices as $service) {
$service->setLocale($event->getRequest()->getDefaultLocale());
}
return;
}
$this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale());
}
public static function getSubscribedEvents(): array
{
return [
// must be registered after the Locale listener
KernelEvents::REQUEST => [['onKernelRequest', 15]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]],
];
}
private function setLocale(string $locale, string $defaultLocale): void
{
foreach ($this->localeAwareServices as $service) {
try {
$service->setLocale($locale);
} catch (\InvalidArgumentException $e) {
$service->setLocale($defaultLocale);
}
}
}
}

View File

@@ -11,43 +11,47 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the locale based on the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class LocaleListener implements EventSubscriberInterface
{
private $router;
private $defaultLocale;
private string $defaultLocale;
private $requestStack;
private bool $useAcceptLanguageHeader;
private array $enabledLocales;
/**
* Constructor.
*
* @param RequestStack $requestStack A RequestStack instance
* @param string $defaultLocale The default locale
* @param RequestContextAwareInterface|null $router The router
*/
public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null)
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 onKernelRequest(GetResponseEvent $event)
public function setDefaultLocale(KernelEvent $event)
{
$event->getRequest()->setDefaultLocale($this->defaultLocale);
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
$request->setDefaultLocale($this->defaultLocale);
$this->setLocale($request);
$this->setRouterContext($request);
@@ -64,6 +68,9 @@ class LocaleListener implements EventSubscriberInterface
{
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
} elseif ($this->useAcceptLanguageHeader && $this->enabledLocales && ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales))) {
$request->setLocale($preferredLanguage);
$request->attributes->set('_vary_by_language', true);
}
}
@@ -74,12 +81,15 @@ class LocaleListener implements EventSubscriberInterface
}
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
// must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
return [
KernelEvents::REQUEST => [
['setDefaultLocale', 100],
// must be registered after the Router to have access to the _locale
['onKernelRequest', 16],
],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
];
}
}

View File

@@ -11,74 +11,73 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
/**
* ProfilerListener collects data for the current request by listening to the kernel events.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ProfilerListener implements EventSubscriberInterface
{
protected $profiler;
protected $matcher;
protected $onlyException;
protected $onlyMasterRequests;
protected $exception;
protected $profiles;
protected $requestStack;
protected $parents;
private $profiler;
private $matcher;
private bool $onlyException;
private bool $onlyMainRequests;
private ?\Throwable $exception = null;
/** @var \SplObjectStorage<Request, Profile> */
private \SplObjectStorage $profiles;
private $requestStack;
private ?string $collectParameter;
/** @var \SplObjectStorage<Request, Request|null> */
private \SplObjectStorage $parents;
/**
* Constructor.
*
* @param Profiler $profiler A Profiler instance
* @param RequestStack $requestStack A RequestStack instance
* @param RequestMatcherInterface|null $matcher A RequestMatcher instance
* @param bool $onlyException true if the profiler only collects data when an exception occurs, false otherwise
* @param bool $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise
* @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, $onlyException = false, $onlyMasterRequests = false)
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 = (bool) $onlyException;
$this->onlyMasterRequests = (bool) $onlyMasterRequests;
$this->onlyException = $onlyException;
$this->onlyMainRequests = $onlyMainRequests;
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requestStack = $requestStack;
$this->collectParameter = $collectParameter;
}
/**
* Handles the onKernelException event.
*
* @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance
*/
public function onKernelException(GetResponseForExceptionEvent $event)
public function onKernelException(ExceptionEvent $event)
{
if ($this->onlyMasterRequests && !$event->isMasterRequest()) {
if ($this->onlyMainRequests && !$event->isMainRequest()) {
return;
}
$this->exception = $event->getException();
$this->exception = $event->getThrowable();
}
/**
* Handles the onKernelResponse event.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
public function onKernelResponse(ResponseEvent $event)
{
$master = $event->isMasterRequest();
if ($this->onlyMasterRequests && !$master) {
if ($this->onlyMainRequests && !$event->isMainRequest()) {
return;
}
@@ -87,6 +86,10 @@ class ProfilerListener implements EventSubscriberInterface
}
$request = $event->getRequest();
if (null !== $this->collectParameter && null !== $collectParameterValue = $request->get($this->collectParameter)) {
true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOLEAN) ? $this->profiler->enable() : $this->profiler->disable();
}
$exception = $this->exception;
$this->exception = null;
@@ -94,8 +97,21 @@ class ProfilerListener implements EventSubscriberInterface
return;
}
if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) {
return;
$session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
if ($session instanceof Session) {
$usageIndexValue = $usageIndexReference = &$session->getUsageIndex();
$usageIndexReference = \PHP_INT_MIN;
}
try {
if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) {
return;
}
} finally {
if ($session instanceof Session) {
$usageIndexReference = $usageIndexValue;
}
}
$this->profiles[$request] = $profile;
@@ -103,12 +119,11 @@ class ProfilerListener implements EventSubscriberInterface
$this->parents[$request] = $this->requestStack->getParentRequest();
}
public function onKernelTerminate(PostResponseEvent $event)
public function onKernelTerminate(TerminateEvent $event)
{
// attach children to parents
foreach ($this->profiles as $request) {
// isset call should be removed when requestStack is required
if (isset($this->parents[$request]) && null !== $parentRequest = $this->parents[$request]) {
if (null !== $parentRequest = $this->parents[$request]) {
if (isset($this->profiles[$parentRequest])) {
$this->profiles[$parentRequest]->addChild($this->profiles[$request]);
}
@@ -124,12 +139,12 @@ class ProfilerListener implements EventSubscriberInterface
$this->parents = new \SplObjectStorage();
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -100),
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
return [
KernelEvents::RESPONSE => ['onKernelResponse', -100],
KernelEvents::EXCEPTION => ['onKernelException', 0],
KernelEvents::TERMINATE => ['onKernelTerminate', -1024],
];
}
}

View File

@@ -11,32 +11,34 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* ResponseListener fixes the Response headers based on the Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ResponseListener implements EventSubscriberInterface
{
private $charset;
private string $charset;
private bool $addContentLanguageHeader;
public function __construct($charset)
public function __construct(string $charset, bool $addContentLanguageHeader = false)
{
$this->charset = $charset;
$this->addContentLanguageHeader = $addContentLanguageHeader;
}
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest()) {
if (!$event->isMainRequest()) {
return;
}
@@ -46,13 +48,21 @@ class ResponseListener implements EventSubscriberInterface
$response->setCharset($this->charset);
}
if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) {
$response->headers->set('Content-Language', $event->getRequest()->getLocale());
}
if ($event->getRequest()->attributes->get('_vary_by_language')) {
$response->setVary('Accept-Language', false);
}
$response->prepare($event->getRequest());
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
return [
KernelEvents::RESPONSE => 'onKernelResponse',
);
];
}
}

View File

@@ -12,25 +12,33 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\NoConfigurationException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the context from the request and sets request attributes based on a matching route.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @final
*/
class RouterListener implements EventSubscriberInterface
{
@@ -38,52 +46,49 @@ class RouterListener implements EventSubscriberInterface
private $context;
private $logger;
private $requestStack;
private ?string $projectDir;
private bool $debug;
/**
* Constructor.
*
* @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher
* @param RequestStack $requestStack A RequestStack instance
* @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface)
* @param LoggerInterface|null $logger The logger
* @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface)
*
* @throws \InvalidArgumentException
*/
public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null)
public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, string $projectDir = null, bool $debug = true)
{
if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
}
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->context = $context ?? $matcher->getContext();
$this->requestStack = $requestStack;
$this->logger = $logger;
$this->projectDir = $projectDir;
$this->debug = $debug;
}
private function setCurrentRequest(Request $request = null)
{
if (null !== $request) {
$this->context->fromRequest($request);
try {
$this->context->fromRequest($request);
} catch (\UnexpectedValueException $e) {
throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode());
}
}
}
/**
* After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator
* operates on the correct context again.
*
* @param FinishRequestEvent $event
*/
public function onKernelFinishRequest(FinishRequestEvent $event)
{
$this->setCurrentRequest($this->requestStack->getParentRequest());
}
public function onKernelRequest(GetResponseEvent $event)
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
@@ -104,19 +109,19 @@ class RouterListener implements EventSubscriberInterface
}
if (null !== $this->logger) {
$this->logger->info('Matched route "{route}".', array(
'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a',
$this->logger->info('Matched route "{route}".', [
'route' => $parameters['_route'] ?? 'n/a',
'route_parameters' => $parameters,
'request_uri' => $request->getUri(),
'method' => $request->getMethod(),
));
]);
}
$request->attributes->add($parameters);
unset($parameters['_route'], $parameters['_controller']);
$request->attributes->set('_route_params', $parameters);
} catch (ResourceNotFoundException $e) {
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUriForPath($request->getPathInfo()));
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
@@ -124,17 +129,41 @@ class RouterListener implements EventSubscriberInterface
throw new NotFoundHttpException($message, $e);
} catch (MethodNotAllowedException $e) {
$message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods()));
$message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getUriForPath($request->getPathInfo()), implode(', ', $e->getAllowedMethods()));
throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
}
}
public static function getSubscribedEvents()
public function onKernelException(ExceptionEvent $event)
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) {
return;
}
if ($e->getPrevious() instanceof NoConfigurationException) {
$event->setResponse($this->createWelcomeResponse());
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 32]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
KernelEvents::EXCEPTION => ['onKernelException', -64],
];
}
private function createWelcomeResponse(): Response
{
$version = Kernel::VERSION;
$projectDir = realpath((string) $this->projectDir).\DIRECTORY_SEPARATOR;
$docVersion = substr(Kernel::VERSION, 0, 3);
ob_start();
include \dirname(__DIR__).'/Resources/welcome.html.php';
return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND);
}
}

View File

@@ -1,66 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Saves the session, in case it is still open, before sending the response/headers.
*
* This ensures several things in case the developer did not save the session explicitly:
*
* * If a session save handler without locking is used, it ensures the data is available
* on the next request, e.g. after a redirect. PHPs auto-save at script end via
* session_register_shutdown is executed after fastcgi_finish_request. So in this case
* the data could be missing the next request because it might not be saved the moment
* the new request is processed.
* * A locking save handler (e.g. the native 'files') circumvents concurrency problems like
* the one above. But by saving the session before long-running things in the terminate event,
* we ensure the session is not blocked longer than needed.
* * When regenerating the session ID no locking is involved in PHPs session design. See
* https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must
* be saved anyway before sending the headers with the new session ID. Otherwise session
* data could get lost again for concurrent requests with the new ID. One result could be
* that you get logged out after just logging in.
*
* This listener should be executed as one of the last listeners, so that previous listeners
* can still operate on the open session. This prevents the overhead of restarting it.
* Listeners after closing the session can still work with the session as usual because
* Symfonys session implementation starts the session on demand. So writing to it after
* it is saved will just restart it.
*
* @author Tobias Schultze <http://tobion.de>
*/
class SaveSessionListener implements EventSubscriberInterface
{
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$session = $event->getRequest()->getSession();
if ($session && $session->isStarted()) {
$session->save();
}
}
public static function getSubscribedEvents()
{
return array(
// low priority but higher than StreamedResponseListener
KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)),
);
}
}

View File

@@ -11,30 +11,23 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Sets the session in the request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.3
* @final
*/
class SessionListener extends AbstractSessionListener
{
private $container;
public function __construct(ContainerInterface $container)
protected function getSession(): ?SessionInterface
{
$this->container = $container;
}
protected function getSession()
{
if (!$this->container->has('session')) {
return;
if ($this->container->has('session_factory')) {
return $this->container->get('session_factory')->createSession();
}
return $this->container->get('session');
return null;
}
}

View File

@@ -11,27 +11,27 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* StreamedResponseListener is responsible for sending the Response
* to the client.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class StreamedResponseListener implements EventSubscriberInterface
{
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest()) {
if (!$event->isMainRequest()) {
return;
}
@@ -42,10 +42,10 @@ class StreamedResponseListener implements EventSubscriberInterface
}
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -1024),
);
return [
KernelEvents::RESPONSE => ['onKernelResponse', -1024],
];
}
}

View File

@@ -11,26 +11,23 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class SurrogateListener implements EventSubscriberInterface
{
private $surrogate;
/**
* Constructor.
*
* @param SurrogateInterface $surrogate An SurrogateInterface instance
*/
public function __construct(SurrogateInterface $surrogate = null)
{
$this->surrogate = $surrogate;
@@ -38,12 +35,10 @@ class SurrogateListener implements EventSubscriberInterface
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest()) {
if (!$event->isMainRequest()) {
return;
}
@@ -63,10 +58,10 @@ class SurrogateListener implements EventSubscriberInterface
$surrogate->addSurrogateControl($event->getResponse());
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
return [
KernelEvents::RESPONSE => 'onKernelResponse',
);
];
}
}

View File

@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Container\ContainerInterface;
/**
* Sets the session in the request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.3
*/
class TestSessionListener extends AbstractTestSessionListener
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function getSession()
{
if (!$this->container->has('session')) {
return;
}
return $this->container->get('session');
}
}

View File

@@ -1,69 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Synchronizes the locale between the request and the translator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TranslatorListener implements EventSubscriberInterface
{
private $translator;
private $requestStack;
public function __construct(TranslatorInterface $translator, RequestStack $requestStack)
{
$this->translator = $translator;
$this->requestStack = $requestStack;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->setLocale($event->getRequest());
}
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if (null === $parentRequest = $this->requestStack->getParentRequest()) {
return;
}
$this->setLocale($parentRequest);
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 10)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
}
private function setLocale(Request $request)
{
try {
$this->translator->setLocale($request->getLocale());
} catch (\InvalidArgumentException $e) {
$this->translator->setLocale($request->getDefaultLocale());
}
}
}

View File

@@ -12,24 +12,24 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Validates Requests.
*
* @author Magnus Nordlander <magnus@fervo.se>
*
* @final
*/
class ValidateRequestListener implements EventSubscriberInterface
{
/**
* Performs the validation.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
@@ -44,12 +44,12 @@ class ValidateRequestListener implements EventSubscriberInterface
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
return array(
KernelEvents::REQUEST => array(
array('onKernelRequest', 256),
),
);
return [
KernelEvents::REQUEST => [
['onKernelRequest', 256],
],
];
}
}

View File

@@ -12,22 +12,13 @@
namespace Symfony\Component\HttpKernel\Exception;
/**
* AccessDeniedHttpException.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class AccessDeniedHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = [])
{
parent::__construct(403, $message, $previous, array(), $code);
parent::__construct(403, $message, $previous, $headers, $code);
}
}

View File

@@ -12,21 +12,12 @@
namespace Symfony\Component\HttpKernel\Exception;
/**
* BadRequestHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class BadRequestHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = [])
{
parent::__construct(400, $message, $previous, array(), $code);
parent::__construct(400, $message, $previous, $headers, $code);
}
}

View File

@@ -12,21 +12,12 @@
namespace Symfony\Component\HttpKernel\Exception;
/**
* ConflictHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class ConflictHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = [])
{
parent::__construct(409, $message, $previous, array(), $code);
parent::__construct(409, $message, $previous, $headers, $code);
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ControllerDoesNotReturnResponseException extends \LogicException
{
public function __construct(string $message, callable $controller, string $file, int $line)
{
parent::__construct($message);
if (!$controllerDefinition = $this->parseControllerDefinition($controller)) {
return;
}
$this->file = $controllerDefinition['file'];
$this->line = $controllerDefinition['line'];
$r = new \ReflectionProperty(\Exception::class, 'trace');
$r->setAccessible(true);
$r->setValue($this, array_merge([
[
'line' => $line,
'file' => $file,
],
], $this->getTrace()));
}
private function parseControllerDefinition(callable $controller): ?array
{
if (\is_string($controller) && str_contains($controller, '::')) {
$controller = explode('::', $controller);
}
if (\is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
return [
'file' => $r->getFileName(),
'line' => $r->getEndLine(),
];
} catch (\ReflectionException $e) {
return null;
}
}
if ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
return [
'file' => $r->getFileName(),
'line' => $r->getEndLine(),
];
}
if (\is_object($controller)) {
$r = new \ReflectionClass($controller);
try {
$line = $r->getMethod('__invoke')->getEndLine();
} catch (\ReflectionException $e) {
$line = $r->getEndLine();
}
return [
'file' => $r->getFileName(),
'line' => $line,
];
}
return null;
}
}

View File

@@ -12,21 +12,12 @@
namespace Symfony\Component\HttpKernel\Exception;
/**
* GoneHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class GoneHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
public function __construct(string $message = '', \Throwable $previous = null, int $code = 0, array $headers = [])
{
parent::__construct(410, $message, $previous, array(), $code);
parent::__construct(410, $message, $previous, $headers, $code);
}
}

View File

@@ -18,10 +18,10 @@ namespace Symfony\Component\HttpKernel\Exception;
*/
class HttpException extends \RuntimeException implements HttpExceptionInterface
{
private $statusCode;
private $headers;
private int $statusCode;
private array $headers;
public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0)
public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
@@ -29,21 +29,16 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
public function getStatusCode(): int
{
return $this->statusCode;
}
public function getHeaders()
public function getHeaders(): array
{
return $this->headers;
}
/**
* Set response headers.
*
* @param array $headers Response headers
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;

Some files were not shown because too many files have changed in this diff Show More