Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -13,26 +13,24 @@ 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\ContainerInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\Finder\Finder;
/**
* 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>
*/
abstract class Bundle implements BundleInterface
{
/**
* @var ContainerInterface
*/
protected $container;
use ContainerAwareTrait;
protected $name;
protected $extension;
protected $path;
private $namespace;
/**
* {@inheritdoc}
@@ -59,15 +57,9 @@ abstract class Bundle implements BundleInterface
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* {@inheritdoc}
* Returns the bundle's container extension.
*
* @return ExtensionInterface|null The container extension
*
* @throws \LogicException
*/
@@ -95,9 +87,7 @@ abstract class Bundle implements BundleInterface
}
}
if ($this->extension) {
return $this->extension;
}
return $this->extension ?: null;
}
/**
@@ -105,9 +95,11 @@ abstract class Bundle implements BundleInterface
*/
public function getNamespace()
{
$class = \get_class($this);
if (null === $this->namespace) {
$this->parseClassName();
}
return substr($class, 0, strrpos($class, '\\'));
return $this->namespace;
}
/**
@@ -135,14 +127,11 @@ abstract class Bundle implements BundleInterface
*/
final public function getName()
{
if (null !== $this->name) {
return $this->name;
if (null === $this->name) {
$this->parseClassName();
}
$name = \get_class($this);
$pos = strrpos($name, '\\');
return $this->name = false === $pos ? $name : substr($name, $pos + 1);
return $this->name;
}
/**
@@ -174,13 +163,16 @@ abstract class Bundle implements BundleInterface
}
$class = $ns.'\\'.$file->getBasename('.php');
if ($this->container) {
$commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : [];
$alias = 'console.command.'.strtolower(str_replace('\\', '_', $class));
if ($this->container->has($alias)) {
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()) {
@trigger_error(sprintf('Auto-registration of the command "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Use PSR-4 based service discovery instead.', $class), E_USER_DEPRECATED);
$application->add($r->newInstance());
}
}
@@ -205,8 +197,15 @@ abstract class Bundle implements BundleInterface
*/
protected function createContainerExtension()
{
if (class_exists($class = $this->getContainerExtensionClass())) {
return new $class();
return class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null;
}
private function parseClassName()
{
$pos = strrpos(static::class, '\\');
$this->namespace = false === $pos ? '' : substr(static::class, 0, $pos);
if (null === $this->name) {
$this->name = false === $pos ? static::class : substr(static::class, $pos + 1);
}
}
}

View File

@@ -54,6 +54,8 @@ interface BundleInterface extends ContainerAwareInterface
* bundle.
*
* @return string The Bundle name it overrides or null if no parent
*
* @deprecated This method is deprecated as of 3.4 and will be removed in 4.0.
*/
public function getParent();

View File

@@ -15,6 +15,8 @@ namespace Symfony\Component\HttpKernel\CacheClearer;
* ChainCacheClearer.
*
* @author Dustin Dobervich <ddobervich@gmail.com>
*
* @final since version 3.4
*/
class ChainCacheClearer implements CacheClearerInterface
{
@@ -25,7 +27,7 @@ class ChainCacheClearer implements CacheClearerInterface
*
* @param array $clearers The initial clearers
*/
public function __construct(array $clearers = array())
public function __construct($clearers = [])
{
$this->clearers = $clearers;
}
@@ -42,9 +44,13 @@ class ChainCacheClearer implements CacheClearerInterface
/**
* Adds a cache clearer to the aggregate.
*
* @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead.
*/
public function add(CacheClearerInterface $clearer)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), E_USER_DEPRECATED);
$this->clearers[] = $clearer;
}
}

View File

@@ -0,0 +1,58 @@
<?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\CacheClearer;
use Psr\Cache\CacheItemPoolInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr6CacheClearer implements CacheClearerInterface
{
private $pools = [];
public function __construct(array $pools = [])
{
$this->pools = $pools;
}
public function addPool(CacheItemPoolInterface $pool)
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 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)
{
return isset($this->pools[$name]);
}
public function clearPool($name)
{
if (!isset($this->pools[$name])) {
throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name));
}
return $this->pools[$name]->clear();
}
/**
* {@inheritdoc}
*/
public function clear($cacheDir)
{
foreach ($this->pools as $pool) {
$pool->clear();
}
}
}

View File

@@ -15,17 +15,21 @@ namespace Symfony\Component\HttpKernel\CacheWarmer;
* Aggregates several cache warmers into a single one.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
*/
class CacheWarmerAggregate implements CacheWarmerInterface
{
protected $warmers = array();
protected $warmers = [];
protected $optionalsEnabled = false;
private $triggerDeprecation = false;
public function __construct(array $warmers = array())
public function __construct($warmers = [])
{
foreach ($warmers as $warmer) {
$this->add($warmer);
}
$this->triggerDeprecation = true;
}
public function enableOptionalWarmers()
@@ -59,16 +63,28 @@ class CacheWarmerAggregate implements CacheWarmerInterface
return false;
}
/**
* @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead.
*/
public function setWarmers(array $warmers)
{
$this->warmers = array();
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), E_USER_DEPRECATED);
$this->warmers = [];
foreach ($warmers as $warmer) {
$this->add($warmer);
}
}
/**
* @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead.
*/
public function add(CacheWarmerInterface $warmer)
{
if ($this->triggerDeprecation) {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), E_USER_DEPRECATED);
}
$this->warmers[] = $warmer;
}
}

View File

@@ -12,7 +12,6 @@
namespace Symfony\Component\HttpKernel;
use Symfony\Component\BrowserKit\Client as BaseClient;
use Symfony\Component\BrowserKit\Cookie as DomCookie;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\Request as DomRequest;
@@ -32,6 +31,7 @@ use Symfony\Component\HttpFoundation\Response;
class Client extends BaseClient
{
protected $kernel;
private $catchExceptions = true;
/**
* @param HttpKernelInterface $kernel An HttpKernel instance
@@ -39,7 +39,7 @@ class Client extends BaseClient
* @param History $history A History instance to store the browser history
* @param CookieJar $cookieJar A CookieJar instance to store the cookies
*/
public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null)
public function __construct(HttpKernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null)
{
// These class properties must be set before calling the parent constructor, as it may depend on it.
$this->kernel = $kernel;
@@ -48,6 +48,16 @@ class Client extends BaseClient
parent::__construct($server, $history, $cookieJar);
}
/**
* Sets whether to catch exceptions when the kernel is handling a request.
*
* @param bool $catchExceptions Whether to catch exceptions
*/
public function catchExceptions($catchExceptions)
{
$this->catchExceptions = $catchExceptions;
}
/**
* Makes a request.
*
@@ -55,7 +65,7 @@ class Client extends BaseClient
*/
protected function doRequest($request)
{
$response = $this->kernel->handle($request);
$response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $this->catchExceptions);
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($request, $response);
@@ -74,21 +84,29 @@ class Client extends BaseClient
$kernel = var_export(serialize($this->kernel), true);
$request = var_export(serialize($request), true);
$r = new \ReflectionClass('\\Symfony\\Component\\ClassLoader\\ClassLoader');
$requirePath = var_export($r->getFileName(), true);
$symfonyPath = var_export(\dirname(\dirname(\dirname(__DIR__))), true);
$errorReporting = error_reporting();
$requires = '';
foreach (get_declared_classes() as $class) {
if (0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$file = \dirname(\dirname($r->getFileName())).'/autoload.php';
if (file_exists($file)) {
$requires .= 'require_once '.var_export($file, true).";\n";
}
}
}
if (!$requires) {
throw new \RuntimeException('Composer autoloader not found.');
}
$code = <<<EOF
<?php
error_reporting($errorReporting);
require_once $requirePath;
\$loader = new Symfony\Component\ClassLoader\ClassLoader();
\$loader->addPrefix('Symfony', $symfonyPath);
\$loader->register();
$requires
\$kernel = unserialize($kernel);
\$request = unserialize($request);
@@ -141,7 +159,7 @@ EOF;
*/
protected function filterFiles(array $files)
{
$filtered = array();
$filtered = [];
foreach ($files as $key => $value) {
if (\is_array($value)) {
$filtered[$key] = $this->filterFiles($value);
@@ -178,20 +196,11 @@ EOF;
*/
protected function filterResponse($response)
{
$headers = $response->headers->all();
if ($response->headers->getCookies()) {
$cookies = array();
foreach ($response->headers->getCookies() as $cookie) {
$cookies[] = new DomCookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
}
$headers['Set-Cookie'] = $cookies;
}
// this is needed to support StreamedResponse
ob_start();
$response->sendContent();
$content = ob_get_clean();
return new DomResponse($content, $response->getStatusCode(), $headers);
return new DomResponse($content, $response->getStatusCode(), $response->headers->all());
}
}

View File

@@ -17,6 +17,8 @@ use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
* EnvParametersResource represents resources stored in prefixed environment variables.
*
* @author Chris Wilkinson <chriswilkinson84@gmail.com>
*
* @deprecated since version 3.4, to be removed in 4.0
*/
class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable
{
@@ -48,11 +50,11 @@ class EnvParametersResource implements SelfCheckingResourceInterface, \Serializa
}
/**
* {@inheritdoc}
* @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);
return ['prefix' => $this->prefix, 'variables' => $this->variables];
}
/**
@@ -63,14 +65,24 @@ class EnvParametersResource implements SelfCheckingResourceInterface, \Serializa
return $this->findVariables() === $this->variables;
}
/**
* @internal
*/
public function serialize()
{
return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables));
return serialize(['prefix' => $this->prefix, 'variables' => $this->variables]);
}
/**
* @internal
*/
public function unserialize($serialized)
{
$unserialized = unserialize($serialized);
if (\PHP_VERSION_ID >= 70000) {
$unserialized = unserialize($serialized, ['allowed_classes' => false]);
} else {
$unserialized = unserialize($serialized);
}
$this->prefix = $unserialized['prefix'];
$this->variables = $unserialized['variables'];
@@ -78,7 +90,7 @@ class EnvParametersResource implements SelfCheckingResourceInterface, \Serializa
private function findVariables()
{
$variables = array();
$variables = [];
foreach ($_SERVER as $key => $value) {
if (0 === strpos($key, $this->prefix)) {

View File

@@ -29,7 +29,7 @@ class FileLocator extends BaseFileLocator
* @param string|null $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, $path = null, array $paths = [])
{
$this->kernel = $kernel;
if (null !== $path) {

View File

@@ -0,0 +1,94 @@
<?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\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;
/**
* Responsible for resolving the arguments passed to an action.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class ArgumentResolver implements ArgumentResolverInterface
{
private $argumentMetadataFactory;
/**
* @var iterable|ArgumentValueResolverInterface[]
*/
private $argumentValueResolvers;
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = [])
{
$this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory();
$this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers();
}
/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
{
$arguments = [];
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
foreach ($this->argumentValueResolvers as $resolver) {
if (!$resolver->supports($request, $metadata)) {
continue;
}
$resolved = $resolver->resolve($request, $metadata);
if (!$resolved instanceof \Generator) {
throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', \get_class($resolver)));
}
foreach ($resolved as $append) {
$arguments[] = $append;
}
// continue to the next controller argument
continue 2;
}
$representative = $controller;
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()));
}
return $arguments;
}
public static function getDefaultArgumentValueResolvers()
{
return [
new RequestAttributeValueResolver(),
new RequestValueResolver(),
new SessionValueResolver(),
new DefaultValueResolver(),
new VariadicValueResolver(),
];
}
}

View File

@@ -0,0 +1,40 @@
<?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;
/**
* Yields the default value defined in the action signature when no value has been given.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class DefaultValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic());
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null;
}
}

View File

@@ -0,0 +1,40 @@
<?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;
/**
* Yields a non-variadic argument's value from the request attributes.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return !$argument->isVariadic() && $request->attributes->has($argument->getName());
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $request->attributes->get($argument->getName());
}
}

View File

@@ -0,0 +1,40 @@
<?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;
/**
* Yields the same instance as the request object passed along.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class RequestValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $request;
}
}

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\Controller\ArgumentResolver;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a service keyed by _controller and argument name.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ServiceValueResolver implements ArgumentValueResolverInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
$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)
{
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));
}
yield $this->container->get($controller)->get($argument->getName());
}
}

View File

@@ -0,0 +1,46 @@
<?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\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields the Session.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class SessionValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
$type = $argument->getType();
if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) {
return false;
}
return $request->getSession() instanceof $type;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $request->getSession();
}
}

View File

@@ -0,0 +1,48 @@
<?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;
/**
* Yields a variadic argument's values from the request attributes.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class VariadicValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return $argument->isVariadic() && $request->attributes->has($argument->getName());
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
$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)));
}
foreach ($values as $value) {
yield $value;
}
}
}

View File

@@ -0,0 +1,34 @@
<?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\HttpFoundation\Request;
/**
* An ArgumentResolverInterface instance knows how to determine the
* arguments for a specific action.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ArgumentResolverInterface
{
/**
* Returns the arguments to pass to the controller.
*
* @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);
}

View File

@@ -0,0 +1,37 @@
<?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\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Responsible for resolving the value of an argument based on its metadata.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
interface ArgumentValueResolverInterface
{
/**
* Whether this resolver can resolve the value for the given ArgumentMetadata.
*
* @return bool
*/
public function supports(Request $request, ArgumentMetadata $argument);
/**
* Returns the possible value(s).
*
* @return \Generator
*/
public function resolve(Request $request, ArgumentMetadata $argument);
}

View File

@@ -0,0 +1,121 @@
<?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 Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
/**
* 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>
*/
class ContainerControllerResolver extends ControllerResolver
{
protected $container;
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
{
$this->container = $container;
parent::__construct($logger);
}
/**
* {@inheritdoc}
*/
public function getController(Request $request)
{
$controller = parent::getController($request);
if (\is_array($controller) && isset($controller[0]) && \is_string($controller[0]) && $this->container->has($controller[0])) {
$controller[0] = $this->instantiateController($controller[0]);
}
return $controller;
}
/**
* 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);
}
$method = null;
if (1 == substr_count($controller, ':')) {
// controller in the "service:method" notation
list($controller, $method) = explode(':', $controller, 2);
}
if (!$this->container->has($controller)) {
$this->throwExceptionIfControllerWasRemoved($controller);
throw new \LogicException(sprintf('Controller not found: service "%s" does not exist.', $controller));
}
$service = $this->container->get($controller);
if (null !== $method) {
return [$service, $method];
}
if (!method_exists($service, '__invoke')) {
throw new \LogicException(sprintf('Controller "%s" cannot be called without a method name. Did you forget an "__invoke" method?', $controller));
}
return $service;
}
/**
* {@inheritdoc}
*/
protected function instantiateController($class)
{
if ($this->container->has($class)) {
return $this->container->get($class);
}
try {
return parent::instantiateController($class);
} catch (\ArgumentCountError $e) {
} catch (\ErrorException $e) {
} catch (\TypeError $e) {
}
$this->throwExceptionIfControllerWasRemoved($class, $e);
throw $e;
}
/**
* @param string $controller
* @param \Exception|\Throwable|null $previous
*/
private function throwExceptionIfControllerWasRemoved($controller, $previous = null)
{
if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) {
throw new \LogicException(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,15 +27,15 @@ use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
class ControllerReference
{
public $controller;
public $attributes = array();
public $query = array();
public $attributes = [];
public $query = [];
/**
* @param string $controller The controller name
* @param array $attributes An array of parameters to add to the Request attributes
* @param array $query An array of parameters to add to the Request query string
*/
public function __construct($controller, array $attributes = array(), array $query = array())
public function __construct($controller, array $attributes = [], array $query = [])
{
$this->controller = $controller;
$this->attributes = $attributes;

View File

@@ -21,7 +21,7 @@ use Symfony\Component\HttpFoundation\Request;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ControllerResolver implements ControllerResolverInterface
class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface
{
private $logger;
@@ -85,10 +85,10 @@ class ControllerResolver implements ControllerResolverInterface
}
}
$callable = $this->createController($controller);
if (!\is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', $controller, $request->getPathInfo()));
try {
$callable = $this->createController($controller);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $e->getMessage()));
}
return $callable;
@@ -96,9 +96,13 @@ class ControllerResolver implements ControllerResolverInterface
/**
* {@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('The "%s()" method 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) {
@@ -112,18 +116,21 @@ class ControllerResolver implements ControllerResolverInterface
}
/**
* @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('The "%s()" method 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();
$arguments = [];
foreach ($parameters as $param) {
if (array_key_exists($param->name, $attributes)) {
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 {
@@ -158,7 +165,7 @@ class ControllerResolver implements ControllerResolverInterface
*
* @return callable A PHP callable
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException When the controller cannot be created
*/
protected function createController($controller)
{
@@ -172,7 +179,13 @@ class ControllerResolver implements ControllerResolverInterface
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
return array($this->instantiateController($class), $method);
$controller = [$this->instantiateController($class), $method];
if (!\is_callable($controller)) {
throw new \InvalidArgumentException($this->getControllerError($controller));
}
return $controller;
}
/**
@@ -186,4 +199,65 @@ class ControllerResolver implements ControllerResolverInterface
{
return new $class();
}
private function getControllerError($callable)
{
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)) {
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 (2 !== \count($callable)) {
return 'Invalid format for controller, expected [controller, method] or controller::method.';
}
list($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;
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);
$alternatives = [];
foreach ($collection as $item) {
$lev = levenshtein($method, $item);
if ($lev <= \strlen($method) / 3 || false !== strpos($item, $method)) {
$alternatives[] = $item;
}
}
asort($alternatives);
$message = sprintf('Expected method "%s" on class "%s"', $method, $className);
if (\count($alternatives) > 0) {
$message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives));
} else {
$message .= sprintf('. Available methods: "%s".', implode('", "', $collection));
}
return $message;
}
}

View File

@@ -31,7 +31,7 @@ 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.
*
* @return callable|false A PHP callable representing the Controller,
@@ -50,6 +50,8 @@ interface ControllerResolverInterface
* @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);
}

View File

@@ -0,0 +1,44 @@
<?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\HttpFoundation\Request;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableArgumentResolver implements ArgumentResolverInterface
{
private $resolver;
private $stopwatch;
public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch)
{
$this->resolver = $resolver;
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
{
$e = $this->stopwatch->start('controller.get_arguments');
$ret = $this->resolver->getArguments($request, $controller);
$e->stop();
return $ret;
}
}

View File

@@ -17,15 +17,26 @@ use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableControllerResolver implements ControllerResolverInterface
class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface
{
private $resolver;
private $stopwatch;
private $argumentResolver;
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch)
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null)
{
$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);
}
}
/**
@@ -44,14 +55,14 @@ class TraceableControllerResolver implements ControllerResolverInterface
/**
* {@inheritdoc}
*
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0.
*/
public function getArguments(Request $request, $controller)
{
$e = $this->stopwatch->start('controller.get_arguments');
@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->resolver->getArguments($request, $controller);
$e->stop();
$ret = $this->argumentResolver->getArguments($request, $controller);
return $ret;
}

View File

@@ -0,0 +1,115 @@
<?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\ControllerMetadata;
/**
* Responsible for storing metadata of an argument.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
class ArgumentMetadata
{
private $name;
private $type;
private $isVariadic;
private $hasDefaultValue;
private $defaultValue;
private $isNullable;
/**
* @param string $name
* @param string $type
* @param bool $isVariadic
* @param bool $hasDefaultValue
* @param mixed $defaultValue
* @param bool $isNullable
*/
public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false)
{
$this->name = $name;
$this->type = $type;
$this->isVariadic = $isVariadic;
$this->hasDefaultValue = $hasDefaultValue;
$this->defaultValue = $defaultValue;
$this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue);
}
/**
* Returns the name as given in PHP, $foo would yield "foo".
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* 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|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns whether the argument is defined as "...$variadic".
*
* @return bool
*/
public function isVariadic()
{
return $this->isVariadic;
}
/**
* Returns whether the argument has a default value.
*
* Implies whether an argument is optional.
*
* @return bool
*/
public function hasDefaultValue()
{
return $this->hasDefaultValue;
}
/**
* Returns whether the argument accepts null values.
*
* @return bool
*/
public function isNullable()
{
return $this->isNullable;
}
/**
* Returns the default value of the argument.
*
* @throws \LogicException if no default value is present; {@see self::hasDefaultValue()}
*
* @return mixed
*/
public function getDefaultValue()
{
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__));
}
return $this->defaultValue;
}
}

View File

@@ -0,0 +1,135 @@
<?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\ControllerMetadata;
/**
* Builds {@see ArgumentMetadata} objects based on the given Controller.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
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)
{
$arguments = [];
if (\is_array($controller)) {
$reflection = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (\is_object($controller) && !$controller instanceof \Closure) {
$reflection = (new \ReflectionObject($controller))->getMethod('__invoke');
} else {
$reflection = new \ReflectionFunction($controller);
}
foreach ($reflection->getParameters() as $param) {
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull());
}
return $arguments;
}
/**
* Returns whether an argument is variadic.
*
* @return bool
*/
private function isVariadic(\ReflectionParameter $parameter)
{
return $this->supportsVariadic && $parameter->isVariadic();
}
/**
* Determines whether an argument has a default value.
*
* @return bool
*/
private function hasDefaultValue(\ReflectionParameter $parameter)
{
return $parameter->isDefaultValueAvailable();
}
/**
* Returns a default value if available.
*
* @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.
*
* @return string|null
*/
private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function)
{
if ($this->supportsParameterType) {
if (!$type = $parameter->getType()) {
return null;
}
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
if ('array' === $name && !$type->isBuiltin()) {
// Special case for HHVM with variadics
return null;
}
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $name)) {
$name = $name[1];
} else {
return null;
}
$lcName = strtolower($name);
if ('self' !== $lcName && 'parent' !== $lcName) {
return $name;
}
if (!$function instanceof \ReflectionMethod) {
return null;
}
if ('self' === $lcName) {
return $function->getDeclaringClass()->name;
}
if ($parent = $function->getDeclaringClass()->getParentClass()) {
return $parent->name;
}
return null;
}
}

View File

@@ -0,0 +1,27 @@
<?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\ControllerMetadata;
/**
* Builds method argument data.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
interface ArgumentMetadataFactoryInterface
{
/**
* @param mixed $controller The controller to resolve the arguments for
*
* @return ArgumentMetadata[]
*/
public function createArgumentMetadata($controller);
}

View File

@@ -26,6 +26,11 @@ class AjaxDataCollector extends DataCollector
// all collecting is done client side
}
public function reset()
{
// all collecting is done client side
}
public function getName()
{
return 'ajax';

View File

@@ -15,11 +15,12 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\VarDumper\Caster\LinkStub;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfigDataCollector extends DataCollector
class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface
{
/**
* @var KernelInterface
@@ -27,6 +28,7 @@ class ConfigDataCollector extends DataCollector
private $kernel;
private $name;
private $version;
private $hasVarDumper;
/**
* @param string $name The name of the application using the web profiler
@@ -36,6 +38,7 @@ class ConfigDataCollector extends DataCollector
{
$this->name = $name;
$this->version = $version;
$this->hasVarDumper = class_exists(LinkStub::class);
}
/**
@@ -51,7 +54,7 @@ class ConfigDataCollector extends DataCollector
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
$this->data = [
'app_name' => $this->name,
'app_version' => $this->version,
'token' => $response->headers->get('X-Debug-Token'),
@@ -61,23 +64,46 @@ class ConfigDataCollector extends DataCollector
'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_timezone' => date_default_timezone_get(),
'xdebug_enabled' => \extension_loaded('xdebug'),
'eaccel_enabled' => \extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'),
'apc_enabled' => \extension_loaded('apc') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN),
'xcache_enabled' => \extension_loaded('xcache') && filter_var(ini_get('xcache.cacher'), FILTER_VALIDATE_BOOLEAN),
'wincache_enabled' => \extension_loaded('wincache') && filter_var(ini_get('wincache.ocenabled'), FILTER_VALIDATE_BOOLEAN),
'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' => array(),
'bundles' => [],
'sapi_name' => \PHP_SAPI,
);
];
if (isset($this->kernel)) {
foreach ($this->kernel->getBundles() as $name => $bundle) {
$this->data['bundles'][$name] = $bundle->getPath();
$this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath();
}
$this->data['symfony_state'] = $this->determineSymfonyState();
$this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_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['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])) {
$this->data['php_version'] = $matches[1];
$this->data['php_version_extra'] = $matches[2];
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
public function lateCollect()
{
$this->data = $this->cloneVar($this->data);
}
public function getApplicationName()
@@ -93,7 +119,7 @@ class ConfigDataCollector extends DataCollector
/**
* Gets the token.
*
* @return string The token
* @return string|null The token
*/
public function getToken()
{
@@ -120,9 +146,37 @@ class ConfigDataCollector extends DataCollector
return $this->data['symfony_state'];
}
public function setCacheVersionInfo($cacheVersionInfo)
/**
* Returns the minor Symfony version used (without patch numbers of extra
* suffix like "RC", "beta", etc.).
*
* @return string
*/
public function getSymfonyMinorVersion()
{
// no-op for BC
return $this->data['symfony_minor_version'];
}
/**
* Returns the human redable date when this Symfony version ends its
* maintenance period.
*
* @return string
*/
public function getSymfonyEom()
{
return $this->data['symfony_eom'];
}
/**
* Returns the human redable date when this Symfony version reaches its
* "end of life" and won't receive bugs or security fixes.
*
* @return string
*/
public function getSymfonyEol()
{
return $this->data['symfony_eol'];
}
/**
@@ -135,6 +189,40 @@ class ConfigDataCollector extends DataCollector
return $this->data['php_version'];
}
/**
* Gets the PHP version extra part.
*
* @return string|null The extra part
*/
public function getPhpVersionExtra()
{
return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null;
}
/**
* @return int The PHP architecture as number of bits (e.g. 32 or 64)
*/
public function getPhpArchitecture()
{
return $this->data['php_architecture'];
}
/**
* @return string
*/
public function getPhpIntlLocale()
{
return $this->data['php_intl_locale'];
}
/**
* @return string
*/
public function getPhpTimezone()
{
return $this->data['php_timezone'];
}
/**
* Gets the application name.
*
@@ -176,23 +264,13 @@ class ConfigDataCollector extends DataCollector
}
/**
* Returns true if EAccelerator is enabled.
* Returns true if APCu is enabled.
*
* @return bool true if EAccelerator is enabled, false otherwise
* @return bool true if APCu is enabled, false otherwise
*/
public function hasEAccelerator()
public function hasApcu()
{
return $this->data['eaccel_enabled'];
}
/**
* Returns true if APC is enabled.
*
* @return bool true if APC is enabled, false otherwise
*/
public function hasApc()
{
return $this->data['apc_enabled'];
return $this->data['apcu_enabled'];
}
/**
@@ -205,36 +283,6 @@ class ConfigDataCollector extends DataCollector
return $this->data['zend_opcache_enabled'];
}
/**
* Returns true if XCache is enabled.
*
* @return bool true if XCache is enabled, false otherwise
*/
public function hasXCache()
{
return $this->data['xcache_enabled'];
}
/**
* Returns true if WinCache is enabled.
*
* @return bool true if WinCache is enabled, false otherwise
*/
public function hasWinCache()
{
return $this->data['wincache_enabled'];
}
/**
* Returns true if any accelerator is enabled.
*
* @return bool true if any accelerator is enabled, false otherwise
*/
public function hasAccelerator()
{
return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache();
}
public function getBundles()
{
return $this->data['bundles'];
@@ -266,8 +314,8 @@ class ConfigDataCollector extends DataCollector
private function determineSymfonyState()
{
$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

@@ -12,6 +12,11 @@
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\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* DataCollector.
@@ -23,21 +28,68 @@ use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
{
protected $data = array();
/**
* @var array|Data
*/
protected $data = [];
/**
* @var ValueExporter
*/
private $valueExporter;
/**
* @var ClonerInterface
*/
private $cloner;
public function serialize()
{
return serialize($this->data);
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$isCalledFromOverridingMethod = isset($trace[1]['function'], $trace[1]['object']) && 'serialize' === $trace[1]['function'] && $this === $trace[1]['object'];
return $isCalledFromOverridingMethod ? $this->data : serialize($this->data);
}
public function unserialize($data)
{
$this->data = unserialize($data);
$this->data = \is_array($data) ? $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)
{
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 Symfony 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);
}
return $this->cloner->cloneVar($var);
}
/**
@@ -46,13 +98,37 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
* @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 Symfony 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 [
'*' => 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) {
$a[$k] = new CutStub($v);
}
}
}
return $a;
},
];
}
}

View File

@@ -18,6 +18,8 @@ use Symfony\Component\HttpFoundation\Response;
* DataCollectorInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @method reset() Resets this data collector to its initial state.
*/
interface DataCollectorInterface
{

View File

@@ -49,12 +49,12 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$this->dumperIsInjected = null !== $dumper;
// All clones share these properties by reference:
$this->rootRefs = array(
$this->rootRefs = [
&$this->data,
&$this->dataCount,
&$this->isCollected,
&$this->clonesCount,
);
];
}
public function __clone()
@@ -71,12 +71,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$this->isCollected = false;
}
$trace = DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS;
if (\PHP_VERSION_ID >= 50400) {
$trace = debug_backtrace($trace, 7);
} else {
$trace = debug_backtrace($trace);
}
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 7);
$file = $trace[0]['file'];
$line = $trace[0]['line'];
@@ -108,7 +103,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ($src) {
$src = explode("\n", $src);
$fileExcerpt = array();
$fileExcerpt = [];
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>';
@@ -158,6 +153,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
) {
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(['fileLinkFormat' => $this->fileLinkFormat]);
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
@@ -168,6 +164,18 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
}
}
public function reset()
{
if ($this->stopwatch) {
$this->stopwatch->reset();
}
$this->data = [];
$this->dataCount = 0;
$this->isCollected = true;
$this->clonesCount = 0;
$this->clonesIndex = 0;
}
public function serialize()
{
if ($this->clonesCount !== $this->clonesIndex) {
@@ -177,7 +185,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$this->data[] = $this->fileLinkFormat;
$this->data[] = $this->charset;
$ser = serialize($this->data);
$this->data = array();
$this->data = [];
$this->dataCount = 0;
$this->isCollected = true;
if (!$this->dumperIsInjected) {
@@ -189,7 +197,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
public function unserialize($data)
{
parent::unserialize($data);
$this->data = unserialize($data);
$charset = array_pop($this->data);
$fileLinkFormat = array_pop($this->data);
$this->dataCount = \count($this->data);
@@ -207,18 +215,14 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ('html' === $format) {
$dumper = new HtmlDumper($data, $this->charset);
$dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format));
}
$dumps = array();
$dumps = [];
foreach ($this->data as $dump) {
if (method_exists($dump['data'], 'withMaxDepth')) {
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
} else {
// getLimitedClone is @deprecated, to be removed in 3.0
$dumper->dump($dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth));
}
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
$dump['data'] = stream_get_contents($data, -1, 0);
ftruncate($data, 0);
rewind($data);
@@ -246,8 +250,9 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
--$i;
}
if (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true) && stripos($h[$i], 'html')) {
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && stripos($h[$i], 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
$this->dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
@@ -257,25 +262,24 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']);
}
$this->data = array();
$this->data = [];
$this->dataCount = 0;
}
}
private function doDump($data, $name, $file, $line)
{
if (\PHP_VERSION_ID >= 50400 && $this->dumper instanceof CliDumper) {
$contextDumper = function ($name, $file, $line, $fileLinkFormat) {
if ($this->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));
$file = strip_tags($this->style('', $file));
if ($fileLinkFormat) {
$link = strtr(strip_tags($this->style('', $fileLinkFormat)), array('%f' => $file, '%l' => (int) $line));
$name = sprintf('<a href="%s" title="%s">'.$s.'</a>', $link, $file, $name);
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>', $file, $name);
$name = sprintf('<abbr title="%s">'.$s.'</abbr>', $f, $name);
}
} else {
$name = $this->style('meta', $name);

View File

@@ -27,6 +27,9 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
public function __construct(EventDispatcherInterface $dispatcher = null)
{
if ($dispatcher instanceof TraceableEventDispatcherInterface && !method_exists($dispatcher, 'reset')) {
@trigger_error(sprintf('Implementing "%s" without the "reset()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', TraceableEventDispatcherInterface::class, \get_class($dispatcher)), E_USER_DEPRECATED);
}
$this->dispatcher = $dispatcher;
}
@@ -35,10 +38,23 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'called_listeners' => array(),
'not_called_listeners' => array(),
);
$this->data = [
'called_listeners' => [],
'not_called_listeners' => [],
];
}
public function reset()
{
$this->data = [];
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
if (!method_exists($this->dispatcher, 'reset')) {
return; // @deprecated
}
$this->dispatcher->reset();
}
}
public function lateCollect()
@@ -47,6 +63,7 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
$this->setCalledListeners($this->dispatcher->getCalledListeners());
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
}
$this->data = $this->cloneVar($this->data);
}
/**

View File

@@ -28,12 +28,20 @@ class ExceptionDataCollector extends DataCollector
public function collect(Request $request, Response $response, \Exception $exception = null)
{
if (null !== $exception) {
$this->data = array(
$this->data = [
'exception' => FlattenException::create($exception),
);
];
}
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
/**
* Checks if the exception is not null.
*
@@ -47,7 +55,7 @@ class ExceptionDataCollector extends DataCollector
/**
* Gets the exception.
*
* @return \Exception The exception
* @return \Exception|FlattenException
*/
public function getException()
{

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
@@ -22,31 +23,20 @@ use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
*/
class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $errorNames = array(
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
E_NOTICE => 'E_NOTICE',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_WARNING => 'E_WARNING',
E_USER_WARNING => 'E_USER_WARNING',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_CORE_WARNING => 'E_CORE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_PARSE => 'E_PARSE',
E_ERROR => 'E_ERROR',
E_CORE_ERROR => 'E_CORE_ERROR',
);
private $logger;
private $containerPathPrefix;
public function __construct($logger = null)
public function __construct($logger = null, $containerPathPrefix = null)
{
if (null !== $logger && $logger instanceof DebugLoggerInterface) {
if (!method_exists($logger, 'clear')) {
@trigger_error(sprintf('Implementing "%s" without the "clear()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', DebugLoggerInterface::class, \get_class($logger)), E_USER_DEPRECATED);
}
$this->logger = $logger;
}
$this->containerPathPrefix = $containerPathPrefix;
}
/**
@@ -60,27 +50,36 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
/**
* {@inheritdoc}
*/
public function lateCollect()
public function reset()
{
if (null !== $this->logger) {
$this->data = $this->computeErrorsCount();
$this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs());
if ($this->logger && method_exists($this->logger, 'clear')) {
$this->logger->clear();
}
$this->data = [];
}
/**
* Gets the logs.
*
* @return array An array of logs
* {@inheritdoc}
*/
public function lateCollect()
{
if (null !== $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));
$this->data = $this->cloneVar($this->data);
}
}
public function getLogs()
{
return isset($this->data['logs']) ? $this->data['logs'] : array();
return isset($this->data['logs']) ? $this->data['logs'] : [];
}
public function getPriorities()
{
return isset($this->data['priorities']) ? $this->data['priorities'] : array();
return isset($this->data['priorities']) ? $this->data['priorities'] : [];
}
public function countErrors()
@@ -93,11 +92,21 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
}
public function countWarnings()
{
return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0;
}
public function countScreams()
{
return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0;
}
public function getCompilerLogs()
{
return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : [];
}
/**
* {@inheritdoc}
*/
@@ -106,106 +115,161 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte
return 'logger';
}
private function getContainerDeprecationLogs()
{
if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) {
return [];
}
if ('' === $logContent = trim(file_get_contents($file))) {
return [];
}
$bootTime = filemtime($file);
$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['priority'] = 100;
$log['priorityName'] = 'DEBUG';
$log['channel'] = '-';
$log['scream'] = false;
unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']);
$logs[] = $log;
}
return $logs;
}
private function getContainerCompilerLogs()
{
if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) {
return [];
}
$logs = [];
foreach (file($file, 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 = ['Unknown Compiler Pass', implode(': ', $log)];
}
$logs[$log[0]][] = ['message' => $log[1]];
}
return $logs;
}
private function sanitizeLogs($logs)
{
$errorContextById = array();
$sanitizedLogs = array();
$sanitizedLogs = [];
$silencedLogs = [];
foreach ($logs as $log) {
$context = $this->sanitizeContext($log['context']);
if (!$this->isSilencedOrDeprecationErrorLog($log)) {
$sanitizedLogs[] = $log;
if (isset($context['type'], $context['file'], $context['line'], $context['level'])) {
$errorId = md5("{$context['type']}/{$context['line']}/{$context['file']}\x00{$log['message']}", true);
$silenced = !($context['type'] & $context['level']);
if (isset($this->errorNames[$context['type']])) {
$context = array_merge(array('name' => $this->errorNames[$context['type']]), $context);
}
continue;
}
if (isset($errorContextById[$errorId])) {
if (isset($errorContextById[$errorId]['errorCount'])) {
++$errorContextById[$errorId]['errorCount'];
} else {
$errorContextById[$errorId]['errorCount'] = 2;
}
if (!$silenced && isset($errorContextById[$errorId]['scream'])) {
unset($errorContextById[$errorId]['scream']);
$errorContextById[$errorId]['level'] = $context['level'];
}
$message = $log['message'];
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
if (isset($silencedLogs[$h = spl_object_hash($exception)])) {
continue;
}
$silencedLogs[$h] = true;
$errorContextById[$errorId] = &$context;
if ($silenced) {
$context['scream'] = true;
if (!isset($sanitizedLogs[$message])) {
$sanitizedLogs[$message] = $log + [
'errorCount' => 0,
'scream' => true,
];
}
$sanitizedLogs[$message]['errorCount'] += $exception->count;
$log['context'] = &$context;
unset($context);
continue;
}
$errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true);
if (isset($sanitizedLogs[$errorId])) {
++$sanitizedLogs[$errorId]['errorCount'];
} else {
$log['context'] = $context;
}
$log += [
'errorCount' => 1,
'scream' => false,
];
$sanitizedLogs[] = $log;
$sanitizedLogs[$errorId] = $log;
}
}
return $sanitizedLogs;
return array_values($sanitizedLogs);
}
private function sanitizeContext($context)
private function isSilencedOrDeprecationErrorLog(array $log)
{
if (\is_array($context)) {
foreach ($context as $key => $value) {
$context[$key] = $this->sanitizeContext($value);
}
return $context;
if (!isset($log['context']['exception'])) {
return false;
}
if (\is_resource($context)) {
return sprintf('Resource(%s)', get_resource_type($context));
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
return true;
}
if (\is_object($context)) {
if ($context instanceof \Exception) {
return sprintf('Exception(%s): %s', \get_class($context), $context->getMessage());
}
return sprintf('Object(%s)', \get_class($context));
if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [E_DEPRECATED, E_USER_DEPRECATED], true)) {
return true;
}
return $context;
return false;
}
private function computeErrorsCount()
private function computeErrorsCount(array $containerDeprecationLogs)
{
$count = array(
$silencedLogs = [];
$count = [
'error_count' => $this->logger->countErrors(),
'deprecation_count' => 0,
'warning_count' => 0,
'scream_count' => 0,
'priorities' => array(),
);
'priorities' => [],
];
foreach ($this->logger->getLogs() 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'];
}
if (isset($log['context']['type'], $log['context']['level'])) {
if (E_DEPRECATED === $log['context']['type'] || E_USER_DEPRECATED === $log['context']['type']) {
if ($this->isSilencedOrDeprecationErrorLog($log)) {
$exception = $log['context']['exception'];
if ($exception instanceof SilencedErrorContext) {
if (isset($silencedLogs[$h = spl_object_hash($exception)])) {
continue;
}
$silencedLogs[$h] = true;
$count['scream_count'] += $exception->count;
} else {
++$count['deprecation_count'];
} elseif (!($log['context']['type'] & $log['context']['level'])) {
++$count['scream_count'];
}
}
}
foreach ($containerDeprecationLogs as $deprecationLog) {
$count['deprecation_count'] += $deprecationLog['context']['exception']->count;
}
ksort($count['priorities']);
return $count;

View File

@@ -23,10 +23,7 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
{
public function __construct()
{
$this->data = array(
'memory' => 0,
'memory_limit' => $this->convertToBytes(ini_get('memory_limit')),
);
$this->reset();
}
/**
@@ -37,6 +34,17 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
$this->updateMemoryUsage();
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [
'memory' => 0,
'memory_limit' => $this->convertToBytes(ini_get('memory_limit')),
];
}
/**
* {@inheritdoc}
*/

View File

@@ -12,16 +12,18 @@
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\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class RequestDataCollector extends DataCollector implements EventSubscriberInterface
class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface
{
protected $controllers;
@@ -35,28 +37,18 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$responseHeaders = $response->headers->all();
foreach ($response->headers->getCookies() as $cookie) {
$responseHeaders['set-cookie'][] = (string) $cookie;
}
// 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 && \is_object($value)) {
$attributes[$key] = $this->varToString($value->getPath());
} elseif ('_route_params' === $key) {
// we need to keep route params as an array (see getRouteParams())
foreach ($value as $k => $v) {
$value[$k] = $this->varToString($v);
}
$attributes[$key] = $value;
if ('_route' === $key) {
$route = \is_object($value) ? $value->getPath() : $value;
$attributes[$key] = $route;
} else {
$attributes[$key] = $this->varToString($value);
$attributes[$key] = $value;
}
}
$content = null;
try {
$content = $request->getContent();
} catch (\LogicException $e) {
@@ -64,9 +56,9 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$content = false;
}
$sessionMetadata = array();
$sessionAttributes = array();
$flashes = array();
$sessionMetadata = [];
$sessionAttributes = [];
$flashes = [];
if ($request->hasSession()) {
$session = $request->getSession();
if ($session->isStarted()) {
@@ -80,7 +72,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$statusCode = $response->getStatusCode();
$this->data = array(
$responseCookies = [];
foreach ($response->headers->getCookies() as $cookie) {
$responseCookies[$cookie->getName()] = $cookie;
}
$this->data = [
'method' => $request->getMethod(),
'format' => $request->getRequestFormat(),
'content' => $content,
'content_type' => $response->headers->get('Content-Type', 'text/html'),
@@ -92,14 +90,16 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
'request_server' => $request->server->all(),
'request_cookies' => $request->cookies->all(),
'request_attributes' => $attributes,
'response_headers' => $responseHeaders,
'route' => $route,
'response_headers' => $response->headers->all(),
'response_cookies' => $responseCookies,
'session_metadata' => $sessionMetadata,
'session_attributes' => $sessionAttributes,
'flashes' => $flashes,
'path_info' => $request->getPathInfo(),
'controller' => 'n/a',
'locale' => $request->getLocale(),
);
];
if (isset($this->data['request_headers']['php-auth-pw'])) {
$this->data['request_headers']['php-auth-pw'] = '******';
@@ -118,56 +118,52 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
continue;
}
if ('request_headers' === $key || 'response_headers' === $key) {
$value = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value);
}
if ('request_server' !== $key && 'request_cookies' !== $key) {
$this->data[$key] = $value;
$this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value);
}
}
if (isset($this->controllers[$request])) {
$controller = $this->controllers[$request];
if (\is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
$this->data['controller'] = array(
'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} catch (\ReflectionException $e) {
if (\is_callable($controller)) {
// using __call or __callStatic
$this->data['controller'] = array(
'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => 'n/a',
'line' => 'n/a',
);
}
}
} elseif ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
$this->data['controller'] = array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} elseif (\is_object($controller)) {
$r = new \ReflectionClass($controller);
$this->data['controller'] = array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} else {
$this->data['controller'] = (string) $controller ?: 'n/a';
}
$this->data['controller'] = $this->parseController($this->controllers[$request]);
unset($this->controllers[$request]);
}
if ($request->attributes->has('_redirected') && $redirectCookie = $request->cookies->get('sf_redirect')) {
$this->data['redirect'] = json_decode($redirectCookie, true);
$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],
])
));
}
$this->data['identifier'] = $this->data['route'] ?: (\is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']);
}
public function lateCollect()
{
$this->data = $this->cloneVar($this->data);
}
public function reset()
{
$this->data = [];
$this->controllers = new \SplObjectStorage();
}
public function getMethod()
{
return $this->data['method'];
}
public function getPathInfo()
@@ -177,52 +173,57 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
public function getRequestRequest()
{
return new ParameterBag($this->data['request_request']);
return new ParameterBag($this->data['request_request']->getValue());
}
public function getRequestQuery()
{
return new ParameterBag($this->data['request_query']);
return new ParameterBag($this->data['request_query']->getValue());
}
public function getRequestHeaders()
{
return new ParameterBag($this->data['request_headers']);
return new ParameterBag($this->data['request_headers']->getValue());
}
public function getRequestServer()
public function getRequestServer($raw = false)
{
return new ParameterBag($this->data['request_server']);
return new ParameterBag($this->data['request_server']->getValue($raw));
}
public function getRequestCookies()
public function getRequestCookies($raw = false)
{
return new ParameterBag($this->data['request_cookies']);
return new ParameterBag($this->data['request_cookies']->getValue($raw));
}
public function getRequestAttributes()
{
return new ParameterBag($this->data['request_attributes']);
return new ParameterBag($this->data['request_attributes']->getValue());
}
public function getResponseHeaders()
{
return new ParameterBag($this->data['response_headers']);
return new ParameterBag($this->data['response_headers']->getValue());
}
public function getResponseCookies()
{
return new ParameterBag($this->data['response_cookies']->getValue());
}
public function getSessionMetadata()
{
return $this->data['session_metadata'];
return $this->data['session_metadata']->getValue();
}
public function getSessionAttributes()
{
return $this->data['session_attributes'];
return $this->data['session_attributes']->getValue();
}
public function getFlashes()
{
return $this->data['flashes'];
return $this->data['flashes']->getValue();
}
public function getContent()
@@ -264,7 +265,12 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
*/
public function getRoute()
{
return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : '';
return $this->data['route'];
}
public function getIdentifier()
{
return $this->data['identifier'];
}
/**
@@ -276,27 +282,53 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
*/
public function getRouteParams()
{
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array();
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : [];
}
/**
* Gets the controller.
* Gets the parsed controller.
*
* @return string The controller as a string
* @return array|string The controller as a string or array of data
* with keys 'class', 'method', 'file' and 'line'
*/
public function getController()
{
return $this->data['controller'];
}
/**
* Gets the previous request attributes.
*
* @return array|bool A legacy array of data from the previous redirection response
* or false otherwise
*/
public function getRedirect()
{
return isset($this->data['redirect']) ? $this->data['redirect'] : false;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if ($event->getRequest()->cookies->has('sf_redirect')) {
$event->getRequest()->attributes->set('_redirected', true);
}
}
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => 'onKernelController');
return [
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
/**
@@ -306,4 +338,78 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
{
return 'request';
}
/**
* 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)
{
if (\is_string($controller) && false !== strpos($controller, '::')) {
$controller = explode('::', $controller);
}
if (\is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
return [
'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
} catch (\ReflectionException $e) {
if (\is_callable($controller)) {
// using __call or __callStatic
return [
'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => 'n/a',
'line' => 'n/a',
];
}
}
}
if ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
$controller = [
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
if (false !== strpos($r->name, '{closure}')) {
return $controller;
}
$controller['method'] = $r->name;
if ($class = $r->getClosureScopeClass()) {
$controller['class'] = $class->name;
} else {
return $r->name;
}
return $controller;
}
if (\is_object($controller)) {
$r = new \ReflectionClass($controller);
return [
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
}
return \is_string($controller) ? $controller : 'n/a';
}
}

View File

@@ -23,17 +23,14 @@ use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
*/
class RouterDataCollector extends DataCollector
{
/**
* @var \SplObjectStorage
*/
protected $controllers;
public function __construct()
{
$this->controllers = new \SplObjectStorage();
$this->data = array(
'redirect' => false,
'url' => null,
'route' => null,
);
$this->reset();
}
/**
@@ -53,6 +50,17 @@ class RouterDataCollector extends DataCollector
unset($this->controllers[$request]);
}
public function reset()
{
$this->controllers = new \SplObjectStorage();
$this->data = [
'redirect' => false,
'url' => null,
'route' => null,
];
}
protected function guessRoute(Request $request, $controller)
{
return 'n/a';

View File

@@ -14,10 +14,10 @@ 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>
*/
class TimeDataCollector extends DataCollector implements LateDataCollectorInterface
@@ -25,7 +25,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
protected $kernel;
protected $stopwatch;
public function __construct(KernelInterface $kernel = null, $stopwatch = null)
public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch = null)
{
$this->kernel = $kernel;
$this->stopwatch = $stopwatch;
@@ -39,14 +39,27 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
if (null !== $this->kernel) {
$startTime = $this->kernel->getStartTime();
} else {
$startTime = $request->server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME'));
$startTime = $request->server->get('REQUEST_TIME_FLOAT');
}
$this->data = array(
$this->data = [
'token' => $response->headers->get('X-Debug-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();
}
}
/**
@@ -63,7 +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)
{
@@ -77,7 +90,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
/**
* Gets the request events.
*
* @return array The request events
* @return StopwatchEvent[] The request events
*/
public function getEvents()
{
@@ -119,13 +132,21 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf
/**
* Gets the request time.
*
* @return int The time
* @return float
*/
public function getStartTime()
{
return $this->data['start_time'];
}
/**
* @return bool whether or not the stopwatch component is installed
*/
public function isStopwatchInstalled()
{
return $this->data['stopwatch_installed'];
}
/**
* {@inheritdoc}
*/

View File

@@ -11,8 +11,12 @@
namespace Symfony\Component\HttpKernel\DataCollector\Util;
@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since Symfony 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
{
@@ -32,7 +36,7 @@ class ValueExporter
}
if (\is_object($value)) {
if ($value instanceof \DateTime || $value instanceof \DateTimeInterface) {
if ($value instanceof \DateTimeInterface) {
return sprintf('Object(%s) - %s', \get_class($value), $value->format(\DateTime::ATOM));
}
@@ -46,7 +50,7 @@ class ValueExporter
$indent = str_repeat(' ', $depth);
$a = array();
$a = [];
foreach ($value as $k => $v) {
if (\is_array($v)) {
$deep = true;
@@ -58,7 +62,13 @@ class ValueExporter
return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1));
}
return sprintf('[%s]', implode(', ', $a));
$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)) {

View File

@@ -0,0 +1,117 @@
<?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\Debug;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Exception\ExceptionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Formats debug file links.
*
* @author Jérémy Romey <jeremy@free-agent.fr>
*/
class FileLinkFormatter implements \Serializable
{
private $fileLinkFormat;
private $requestStack;
private $baseDir;
private $urlFormat;
/**
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
*/
public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $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 = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE);
}
$this->fileLinkFormat = $fileLinkFormat;
$this->requestStack = $requestStack;
$this->baseDir = $baseDir;
$this->urlFormat = $urlFormat;
}
public function format($file, $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));
break;
}
}
return strtr($fmt[0], ['%f' => $file, '%l' => $line]);
}
return false;
}
/**
* @internal
*/
public function serialize()
{
return serialize($this->getFileLinkFormat());
}
/**
* @internal
*/
public function unserialize($serialized)
{
if (\PHP_VERSION_ID >= 70000) {
$this->fileLinkFormat = unserialize($serialized, ['allowed_classes' => false]);
} else {
$this->fileLinkFormat = unserialize($serialized);
}
}
/**
* @internal
*/
public static function generateUrlFormat(UrlGeneratorInterface $router, $routeName, $queryString)
{
try {
return $router->generate($routeName).$queryString;
} catch (ExceptionInterface $e) {
return null;
}
}
private function getFileLinkFormat()
{
if ($this->fileLinkFormat) {
return $this->fileLinkFormat;
}
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
$request = $this->requestStack->getMasterRequest();
if ($request instanceof Request) {
if ($this->urlFormat instanceof \Closure && !$this->urlFormat = \call_user_func($this->urlFormat)) {
return null;
}
return [
$request->getSchemeAndHttpHost().$this->urlFormat,
$this->baseDir.\DIRECTORY_SEPARATOR, '',
];
}
}
return null;
}
}

View File

@@ -14,7 +14,6 @@ namespace Symfony\Component\HttpKernel\Debug;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profiler;
/**
* Collects some data about event listeners.
@@ -25,22 +24,6 @@ use Symfony\Component\HttpKernel\Profiler\Profiler;
*/
class TraceableEventDispatcher extends BaseTraceableEventDispatcher
{
/**
* Sets the profiler.
*
* The traceable event dispatcher does not use the profiler anymore.
* The job is now done directly by the Profiler listener and the
* data collectors themselves.
*
* @param Profiler|null $profiler A Profiler instance
*
* @deprecated since version 2.4, to be removed in 3.0.
*/
public function setProfiler(Profiler $profiler = null)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
}
/**
* {@inheritdoc}
*/
@@ -59,6 +42,9 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
break;
case KernelEvents::TERMINATE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
if (null === $token) {
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
@@ -78,17 +64,23 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
protected function postDispatch($eventName, Event $event)
{
switch ($eventName) {
case KernelEvents::CONTROLLER:
case KernelEvents::CONTROLLER_ARGUMENTS:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
if (null === $token) {
break;
}
$this->stopwatch->stopSection($token);
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');
if (null === $token) {
break;
}
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {

View File

@@ -0,0 +1,153 @@
<?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 Composer\Autoload\ClassLoader;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
/**
* Sets the classes to compile in the cache for the container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddAnnotatedClassesToCachePass implements CompilerPassInterface
{
private $kernel;
public function __construct(Kernel $kernel)
{
$this->kernel = $kernel;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$classes = [];
$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());
}
}
$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));
}
/**
* Expands the given class patterns using a list of existing classes.
*
* @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 derived from the patterns
*/
private function expandClasses(array $patterns, array $classes)
{
$expanded = [];
// Explicit classes declared in the patterns are returned directly
foreach ($patterns as $key => $pattern) {
if ('\\' !== substr($pattern, -1) && false === strpos($pattern, '*')) {
unset($patterns[$key]);
$expanded[] = ltrim($pattern, '\\');
}
}
// Match patterns with the classes list
$regexps = $this->patternsToRegexps($patterns);
foreach ($classes as $class) {
$class = ltrim($class, '\\');
if ($this->matchAnyRegexps($class, $regexps)) {
$expanded[] = $class;
}
}
return array_unique($expanded);
}
private function getClassesInComposerClassMaps()
{
$classes = [];
foreach (spl_autoload_functions() as $function) {
if (!\is_array($function)) {
continue;
}
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
}
if (\is_array($function) && $function[0] instanceof ClassLoader) {
$classes += array_filter($function[0]->getClassMap());
}
}
return array_keys($classes);
}
private function patternsToRegexps($patterns)
{
$regexps = [];
foreach ($patterns as $pattern) {
// Escape user input
$regex = preg_quote(ltrim($pattern, '\\'));
// Wildcards * and **
$regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']);
// If this class does not end by a slash, anchor the end
if ('\\' !== substr($regex, -1)) {
$regex .= '$';
}
$regexps[] = '{^\\\\'.$regex.'}';
}
return $regexps;
}
private function matchAnyRegexps($class, $regexps)
{
$blacklisted = false !== strpos($class, 'Test');
foreach ($regexps as $regex) {
if ($blacklisted && false === strpos($regex, 'Test')) {
continue;
}
if (preg_match($regex, '\\'.$class)) {
return true;
}
}
return false;
}
}

View File

@@ -11,36 +11,15 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since Symfony 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 implements CompilerPassInterface
class AddClassesToCachePass extends AddAnnotatedClassesToCachePass
{
private $kernel;
public function __construct(Kernel $kernel)
{
$this->kernel = $kernel;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$classes = array();
foreach ($container->getExtensions() as $extension) {
if ($extension instanceof Extension) {
$classes = array_merge($classes, $extension->getClassesToCompile());
}
}
$this->kernel->setClassCache(array_unique($container->getParameterBag()->resolveValue($classes)));
}
}

View File

@@ -0,0 +1,48 @@
<?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\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Gathers and configures the argument value resolvers.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
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)) {
return;
}
$container
->getDefinition($this->argumentResolverService)
->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container)))
;
}
}

View File

@@ -20,25 +20,58 @@ use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension;
*/
abstract class Extension extends BaseExtension
{
private $classes = array();
private $classes = [];
private $annotatedClasses = [];
/**
* 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 Symfony 3.3, to be removed in 4.0.', E_USER_DEPRECATED);
}
return $this->classes;
}
/**
* Gets the annotated classes to cache.
*
* @return array An array of classes
*/
public function getAnnotatedClassesToCompile()
{
return $this->annotatedClasses;
}
/**
* Adds classes to the class cache.
*
* @param array $classes An array of classes
* @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 Symfony 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.
*
* @param array $annotatedClasses An array of class patterns
*/
public function addAnnotatedClassesToCompile(array $annotatedClasses)
{
$this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses);
}
}

View File

@@ -12,8 +12,11 @@
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\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
/**
* Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies.
@@ -42,37 +45,23 @@ class FragmentRendererPass implements CompilerPassInterface
}
$definition = $container->getDefinition($this->handlerService);
foreach ($container->findTaggedServiceIds($this->rendererTag) as $id => $tags) {
$renderers = [];
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) {
$def = $container->getDefinition($id);
if (!$def->isPublic()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must be public as fragment renderer are lazy-loaded.', $id));
}
if ($def->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as fragment renderer are lazy-loaded.', $id));
}
$class = $container->getParameterBag()->resolveValue($def->getClass());
$interface = 'Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface';
if (!is_subclass_of($class, $interface)) {
if (!class_exists($class, false)) {
throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(FragmentRendererInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class));
}
foreach ($tags as $tag) {
if (!isset($tag['alias'])) {
@trigger_error(sprintf('Service "%s" will have to define the "alias" attribute on the "%s" tag as of Symfony 3.0.', $id, $this->rendererTag), E_USER_DEPRECATED);
// register the handler as a non-lazy-loaded one
$definition->addMethodCall('addRenderer', array(new Reference($id)));
} else {
$definition->addMethodCall('addRendererService', array($tag['alias'], $id));
}
$renderers[$tag['alias']] = new Reference($id);
}
}
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers));
}
}

View File

@@ -11,7 +11,7 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
@@ -23,30 +23,22 @@ use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
class LazyLoadingFragmentHandler extends FragmentHandler
{
private $container;
private $rendererIds = array();
/**
* @deprecated since version 3.3, to be removed in 4.0
*/
private $rendererIds = [];
private $initialized = [];
/**
* RequestStack will become required in 3.0.
*
* @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
*/
public function __construct(ContainerInterface $container, $requestStack = null, $debug = false)
public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false)
{
$this->container = $container;
if ((null !== $requestStack && !$requestStack instanceof RequestStack) || $debug instanceof RequestStack) {
$tmp = $debug;
$debug = $requestStack;
$requestStack = \func_num_args() < 3 ? null : $tmp;
@trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
} elseif (!$requestStack instanceof RequestStack) {
@trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
}
parent::__construct($requestStack, array(), $debug);
parent::__construct($requestStack, [], $debug);
}
/**
@@ -54,21 +46,32 @@ class LazyLoadingFragmentHandler extends FragmentHandler
*
* @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 Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
$this->rendererIds[$name] = $renderer;
}
/**
* {@inheritdoc}
*/
public function render($uri, $renderer = 'inline', array $options = array())
public function render($uri, $renderer = 'inline', array $options = [])
{
// 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;
}
return parent::render($uri, $renderer, $options);

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

@@ -32,7 +32,7 @@ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPas
{
foreach ($this->extensions as $extension) {
if (!\count($container->getExtensionConfig($extension))) {
$container->loadFromExtension($extension, array());
$container->loadFromExtension($extension, []);
}
}

View File

@@ -0,0 +1,179 @@
<?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\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
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;
/**
* Creates the service-locators required by ServiceValueResolver.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
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)) {
return;
}
$parameterBag = $container->getParameterBag();
$controllers = [];
foreach ($container->findTaggedServiceIds($this->controllerTag, 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 ($def instanceof ChildDefinition) {
$def = $container->findDefinition($def->getParent());
$class = $class ?: $def->getClass();
$bindings += $def->getBindings();
}
$class = $parameterBag->resolveValue($class);
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
$isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class);
// get regular public methods
$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)] = [$r, $r->getParameters()];
}
}
// validate and collect explicit per-actions and per-arguments service references
foreach ($tags as $attributes) {
if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) {
$autowire = true;
continue;
}
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));
}
}
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));
}
list($r, $parameters) = $methods[$action];
$found = false;
foreach ($parameters as $p) {
if ($attributes['argument'] === $p->name) {
if (!isset($arguments[$r->name][$p->name])) {
$arguments[$r->name][$p->name] = $attributes['id'];
}
$found = true;
break;
}
}
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));
}
}
foreach ($methods as list($r, $parameters)) {
/** @var \ReflectionMethod $r */
// create a per-method map of argument-names to service/type-references
$args = [];
foreach ($parameters as $p) {
/** @var \ReflectionParameter $p */
$type = $target = ProxyHelper::getTypeHint($r, $p, true);
$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;
} 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));
} elseif ($p->allowsNull() && !$p->isOptional()) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
}
} elseif (isset($bindings[$bindingName = '$'.$p->name]) || isset($bindings[$bindingName = $type])) {
$binding = $bindings[$bindingName];
list($bindingValue, $bindingId) = $binding->getValues();
if (!$bindingValue instanceof Reference) {
continue;
}
$binding->setValues([$bindingValue, $bindingId, true]);
$args[$p->name] = $bindingValue;
continue;
} elseif (!$type || !$autowire) {
continue;
}
if (Request::class === $type) {
continue;
}
if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) {
$message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type);
// see if the type-hint lives in the same namespace as the controller
if (0 === strncmp($type, $class, strrpos($class, '\\'))) {
$message .= ' Did you forget to add a use statement?';
}
throw new InvalidArgumentException($message);
}
$args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior);
}
// register the maps as a per-method service-locators
if ($args) {
$controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args);
}
}
}
$container->getDefinition($this->resolverServiceId)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers));
}
}

View File

@@ -0,0 +1,76 @@
<?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\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Removes empty service-locators registered for ServiceValueResolver.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
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));
$controllers = $controllerLocator->getArgument(0);
foreach ($controllers as $controller => $argumentRef) {
$argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]);
if (!$argumentLocator->getArgument(0)) {
// remove empty argument locators
$reason = sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller);
} 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));
$controllerDef = $container->getDefinition($id);
foreach ($controllerDef->getMethodCalls() as list($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;
}
if ('__invoke' === $action) {
$controllers[$id] = $argumentRef;
}
continue;
}
}
unset($controllers[$controller]);
$container->log($this, $reason);
}
$controllerLocator->replaceArgument(0, $controllers);
}
}

View File

@@ -0,0 +1,66 @@
<?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
{
private $tagName;
public function __construct($tagName = 'kernel.reset')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has('services_resetter')) {
return;
}
$services = $methods = [];
foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) {
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
$attributes = $tags[0];
if (!isset($attributes['method'])) {
throw new RuntimeException(sprintf('Tag %s requires the "method" attribute to be set.', $this->tagName));
}
$methods[$id] = $attributes['method'];
}
if (empty($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,39 @@
<?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;
/**
* Resets provided services.
*
* @author Alexander M. Turek <me@derrabus.de>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ServicesResetter
{
private $resettableServices;
private $resetMethods;
public function __construct(\Traversable $resettableServices, array $resetMethods)
{
$this->resettableServices = $resettableServices;
$this->resetMethods = $resetMethods;
}
public function reset()
{
foreach ($this->resettableServices as $id => $service) {
$service->{$this->resetMethods[$id]}();
}
}
}

View File

@@ -0,0 +1,52 @@
<?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\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows filtering of controller arguments.
*
* You can call getController() to retrieve the controller and getArguments
* to retrieve the current arguments. With setArguments() you can replace
* arguments that are used to call the controller.
*
* Arguments set in the event must be compatible with the signature of the
* controller.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class FilterControllerArgumentsEvent extends FilterControllerEvent
{
private $arguments;
public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType)
{
parent::__construct($kernel, $controller, $request, $requestType);
$this->arguments = $arguments;
}
/**
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
}
}

View File

@@ -29,7 +29,7 @@ class FilterControllerEvent extends KernelEvent
{
private $controller;
public function __construct(HttpKernelInterface $kernel, $controller, Request $request, $requestType)
public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType)
{
parent::__construct($kernel, $request, $requestType);
@@ -46,54 +46,8 @@ class FilterControllerEvent extends KernelEvent
return $this->controller;
}
/**
* Sets a new controller.
*
* @param callable $controller
*
* @throws \LogicException
*/
public function setController($controller)
public function setController(callable $controller)
{
// controller must be a callable
if (!\is_callable($controller)) {
throw new \LogicException(sprintf('The controller must be a callable (%s given).', $this->varToString($controller)));
}
$this->controller = $controller;
}
private function varToString($var)
{
if (\is_object($var)) {
return sprintf('Object(%s)', \get_class($var));
}
if (\is_array($var)) {
$a = array();
foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v));
}
return sprintf('Array(%s)', implode(', ', $a));
}
if (\is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
}
}

View File

@@ -36,6 +36,11 @@ class GetResponseForExceptionEvent extends GetResponseEvent
*/
private $exception;
/**
* @var bool
*/
private $allowCustomResponseCode = false;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e)
{
parent::__construct($kernel, $request, $requestType);
@@ -64,4 +69,22 @@ class GetResponseForExceptionEvent extends GetResponseEvent
{
$this->exception = $exception;
}
/**
* Mark the event as allowing a custom response code.
*/
public function allowCustomResponseCode()
{
$this->allowCustomResponseCode = true;
}
/**
* Returns true if the event allows a custom response code.
*
* @return bool
*/
public function isAllowingCustomResponseCode()
{
return $this->allowCustomResponseCode;
}
}

View File

@@ -0,0 +1,92 @@
<?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\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Sets the session in the request.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class AbstractSessionListener implements EventSubscriberInterface
{
private $sessionUsageStack = [];
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $this->getSession();
$this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : null;
if (null === $session || $request->hasSession()) {
return;
}
$request->setSession($session);
}
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (!$session = $event->getRequest()->getSession()) {
return;
}
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
$event->getResponse()
->setExpires(new \DateTime())
->setPrivate()
->setMaxAge(0)
->headers->addCacheControlDirective('must-revalidate');
}
}
/**
* @internal
*/
public function onFinishRequest(FinishRequestEvent $event)
{
if ($event->isMasterRequest()) {
array_pop($this->sessionUsageStack);
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 128],
// low priority to come after regular response listeners, same as SaveSessionListener
KernelEvents::RESPONSE => ['onKernelResponse', -1000],
KernelEvents::FINISH_REQUEST => ['onFinishRequest'],
];
}
/**
* Gets the session object.
*
* @return SessionInterface|null A SessionInterface instance or null if no session is available
*/
abstract protected function getSession();
}

View File

@@ -0,0 +1,100 @@
<?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\Cookie;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* TestSessionListener.
*
* Saves session in test environment.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractTestSessionListener implements EventSubscriberInterface
{
private $sessionId;
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())) {
$this->sessionId = $cookies->get($session->getName());
$session->setId($this->sessionId);
}
}
/**
* Checks if session was initialized and saves if current request is master
* Runs on 'kernel.response' in test environment.
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (!$session = $event->getRequest()->getSession()) {
return;
}
if ($wasStarted = $session->isStarted()) {
$session->save();
}
if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) {
$params = session_get_cookie_params();
foreach ($event->getResponse()->headers->getCookies() as $cookie) {
if ($session->getName() === $cookie->getName() && $params['path'] === $cookie->getPath() && $params['domain'] == $cookie->getDomain()) {
return;
}
}
$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']));
$this->sessionId = $session->getId();
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 192],
KernelEvents::RESPONSE => ['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

@@ -45,6 +45,6 @@ class AddRequestFormatsListener implements EventSubscriberInterface
*/
public static function getSubscribedEvents()
{
return array(KernelEvents::REQUEST => array('onKernelRequest', 1));
return [KernelEvents::REQUEST => ['onKernelRequest', 1]];
}
}

View File

@@ -19,6 +19,7 @@ use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -35,25 +36,28 @@ class DebugHandlersListener implements EventSubscriberInterface
private $throwAt;
private $scream;
private $fileLinkFormat;
private $scope;
private $firstCall = true;
private $hasTerminatedWithException;
/**
* @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 $fileLinkFormat The format for links to source files
* @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|FileLinkFormatter|null $fileLinkFormat The format for links to source files
* @param bool $scope Enables/disables scoping mode
*/
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null)
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true)
{
$this->exceptionHandler = $exceptionHandler;
$this->logger = $logger;
$this->levels = $levels;
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null));
$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 ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->fileLinkFormat = $fileLinkFormat;
$this->scope = (bool) $scope;
}
/**
@@ -75,15 +79,20 @@ class DebugHandlersListener implements EventSubscriberInterface
if ($this->logger) {
$handler->setDefaultLogger($this->logger, $this->levels);
if (\is_array($this->levels)) {
$scream = 0;
$levels = 0;
foreach ($this->levels as $type => $log) {
$scream |= $type;
$levels |= $type;
}
} else {
$scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels;
$levels = $this->levels;
}
if ($this->scream) {
$handler->screamAt($scream);
$handler->screamAt($levels);
}
if ($this->scope) {
$handler->scopeAt($levels & ~E_USER_DEPRECATED & ~E_DEPRECATED);
} else {
$handler->scopeAt(0, true);
}
$this->logger = $this->levels = null;
}
@@ -97,7 +106,7 @@ class DebugHandlersListener implements EventSubscriberInterface
if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
$request = $event->getRequest();
$hasRun = &$this->hasTerminatedWithException;
$this->exceptionHandler = function (\Exception $e) use ($kernel, $request, &$hasRun) {
$this->exceptionHandler = static function (\Exception $e) use ($kernel, $request, &$hasRun) {
if ($hasRun) {
throw $e;
}
@@ -137,10 +146,10 @@ class DebugHandlersListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
$events = array(KernelEvents::REQUEST => array('configure', 2048));
$events = [KernelEvents::REQUEST => ['configure', 2048]];
if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = array('configure', 2048);
if ('cli' === \PHP_SAPI && \defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
}
return $events;

View File

@@ -45,7 +45,11 @@ class DumpListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
if (!class_exists(ConsoleEvents::class)) {
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

@@ -56,15 +56,14 @@ class ExceptionListener implements EventSubscriberInterface
} 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()) {
$prev = $e;
do {
if ($exception === $wrapper = $prev) {
throw $e;
}
}
} while ($prev = $wrapper->getPrevious());
$prev = new \ReflectionProperty('Exception', 'previous');
$prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
$prev->setAccessible(true);
$prev->setValue($wrapper, $exception);
@@ -84,9 +83,9 @@ class ExceptionListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', -128),
);
return [
KernelEvents::EXCEPTION => ['onKernelException', -128],
];
}
/**
@@ -99,9 +98,9 @@ class ExceptionListener implements EventSubscriberInterface
{
if (null !== $this->logger) {
if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, array('exception' => $exception));
$this->logger->critical($message, ['exception' => $exception]);
} else {
$this->logger->error($message, array('exception' => $exception));
$this->logger->error($message, ['exception' => $exception]);
}
}
}
@@ -116,15 +115,11 @@ class ExceptionListener implements EventSubscriberInterface
*/
protected function duplicateRequest(\Exception $exception, Request $request)
{
$attributes = array(
$attributes = [
'_controller' => $this->controller,
'exception' => FlattenException::create($exception),
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
// keep for BC -- as $format can be an argument of the controller callable
// see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
// @deprecated since version 2.4, to be removed in 3.0
'format' => $request->getRequestFormat(),
);
];
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');

View File

@@ -24,7 +24,7 @@ use Symfony\Component\HttpKernel\UriSigner;
* 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>
@@ -70,7 +70,7 @@ class FragmentListener implements EventSubscriberInterface
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');
}
@@ -90,22 +90,10 @@ class FragmentListener implements EventSubscriberInterface
throw new AccessDeniedHttpException();
}
/**
* @deprecated since version 2.3.19, to be removed in 3.0.
*
* @return string[]
*/
protected function getLocalIpAddresses()
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3.19 and will be removed in 3.0.', E_USER_DEPRECATED);
return array('127.0.0.1', 'fe80::1', '::1');
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 48)),
);
return [
KernelEvents::REQUEST => [['onKernelRequest', 48]],
];
}
}

View File

@@ -22,11 +22,6 @@ use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the locale based on the current request.
*
* This listener works in 2 modes:
*
* * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
* * 2.4+ mode where you must pass a RequestStack instance in the constructor.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LocaleListener implements EventSubscriberInterface
@@ -36,62 +31,17 @@ class LocaleListener implements EventSubscriberInterface
private $requestStack;
/**
* RequestStack will become required in 3.0.
*
* @param RequestStack $requestStack A RequestStack instance
* @param string $defaultLocale The default locale
* @param RequestContextAwareInterface|null $router The router
*
* @throws \InvalidArgumentException
*/
public function __construct($requestStack = null, $defaultLocale = 'en', $router = null)
public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null)
{
if ((null !== $requestStack && !$requestStack instanceof RequestStack) || $defaultLocale instanceof RequestContextAwareInterface || $router instanceof RequestStack) {
$tmp = $router;
$router = \func_num_args() < 2 ? null : $defaultLocale;
$defaultLocale = $requestStack;
$requestStack = \func_num_args() < 3 ? null : $tmp;
@trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as first argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
} elseif (!$requestStack instanceof RequestStack) {
@trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
}
if (null !== $requestStack && !$requestStack instanceof RequestStack) {
throw new \InvalidArgumentException('RequestStack instance expected.');
}
if (null !== $router && !$router instanceof RequestContextAwareInterface) {
throw new \InvalidArgumentException('Router must implement RequestContextAwareInterface.');
}
$this->defaultLocale = $defaultLocale;
$this->requestStack = $requestStack;
$this->router = $router;
}
/**
* Sets the current Request.
*
* This method was used to synchronize the Request, but as the HttpKernel
* is doing that automatically now, you should never call it directly.
* It is kept public for BC with the 2.3 version.
*
* @param Request|null $request A Request instance
*
* @deprecated since version 2.4, to be removed in 3.0.
*/
public function setRequest(Request $request = null)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
if (null === $request) {
return;
}
$this->setLocale($request);
$this->setRouterContext($request);
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
@@ -103,10 +53,6 @@ class LocaleListener implements EventSubscriberInterface
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if (null === $this->requestStack) {
return; // removed when requestStack is required
}
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
$this->setRouterContext($parentRequest);
}
@@ -128,10 +74,10 @@ class LocaleListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
return [
// 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)),
);
KernelEvents::REQUEST => [['onKernelRequest', 16]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
];
}
}

View File

@@ -15,7 +15,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -33,7 +32,6 @@ class ProfilerListener implements EventSubscriberInterface
protected $onlyException;
protected $onlyMasterRequests;
protected $exception;
protected $requests = array();
protected $profiles;
protected $requestStack;
protected $parents;
@@ -45,27 +43,8 @@ class ProfilerListener implements EventSubscriberInterface
* @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
*/
public function __construct(Profiler $profiler, $requestStack = null, $matcher = null, $onlyException = false, $onlyMasterRequests = false)
public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false)
{
if ($requestStack instanceof RequestMatcherInterface || (null !== $matcher && !$matcher instanceof RequestMatcherInterface) || $onlyMasterRequests instanceof RequestStack) {
$tmp = $onlyMasterRequests;
$onlyMasterRequests = $onlyException;
$onlyException = $matcher;
$matcher = $requestStack;
$requestStack = \func_num_args() < 5 ? null : $tmp;
@trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::onKernelRequest method will be removed in 3.0.', E_USER_DEPRECATED);
} elseif (!$requestStack instanceof RequestStack) {
@trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::onKernelRequest method will be removed in 3.0.', E_USER_DEPRECATED);
}
if (null !== $requestStack && !$requestStack instanceof RequestStack) {
throw new \InvalidArgumentException('RequestStack instance expected.');
}
if (null !== $matcher && !$matcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must implement RequestMatcherInterface.');
}
$this->profiler = $profiler;
$this->matcher = $matcher;
$this->onlyException = (bool) $onlyException;
@@ -87,16 +66,6 @@ class ProfilerListener implements EventSubscriberInterface
$this->exception = $event->getException();
}
/**
* @deprecated since version 2.4, to be removed in 3.0.
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (null === $this->requestStack) {
$this->requests[] = $event->getRequest();
}
}
/**
* Handles the onKernelResponse event.
*/
@@ -125,22 +94,14 @@ class ProfilerListener implements EventSubscriberInterface
$this->profiles[$request] = $profile;
if (null !== $this->requestStack) {
$this->parents[$request] = $this->requestStack->getParentRequest();
} elseif (!$master) {
// to be removed when requestStack is required
array_pop($this->requests);
$this->parents[$request] = end($this->requests);
}
$this->parents[$request] = $this->requestStack->getParentRequest();
}
public function onKernelTerminate(PostResponseEvent $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]);
}
@@ -154,18 +115,14 @@ class ProfilerListener implements EventSubscriberInterface
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requests = array();
}
public static function getSubscribedEvents()
{
return array(
// kernel.request must be registered as early as possible to not break
// when an exception is thrown in any other kernel.request listener
KernelEvents::REQUEST => array('onKernelRequest', 1024),
KernelEvents::RESPONSE => array('onKernelResponse', -100),
return [
KernelEvents::RESPONSE => ['onKernelResponse', -100],
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
KernelEvents::TERMINATE => ['onKernelTerminate', -1024],
];
}
}

View File

@@ -49,8 +49,8 @@ class ResponseListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
return [
KernelEvents::RESPONSE => 'onKernelResponse',
);
];
}
}

View File

@@ -15,13 +15,17 @@ use Psr\Log\LoggerInterface;
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\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
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;
@@ -31,54 +35,30 @@ use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the context from the request and sets request attributes based on a matching route.
*
* This listener works in 2 modes:
*
* * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
* * 2.4+ mode where you must pass a RequestStack instance in the constructor.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class RouterListener implements EventSubscriberInterface
{
private $matcher;
private $context;
private $logger;
private $request;
private $requestStack;
private $projectDir;
private $debug;
/**
* RequestStack will become required in 3.0.
*
* @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 string $projectDir
* @param bool $debug
*
* @throws \InvalidArgumentException
*/
public function __construct($matcher, $requestStack = null, $context = null, $logger = null)
public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, $projectDir = null, $debug = true)
{
if ($requestStack instanceof RequestContext || $context instanceof LoggerInterface || $logger instanceof RequestStack) {
$tmp = $requestStack;
$requestStack = $logger;
$logger = $context;
$context = $tmp;
@trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
} elseif (!$requestStack instanceof RequestStack) {
@trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
}
if (null !== $requestStack && !$requestStack instanceof RequestStack) {
throw new \InvalidArgumentException('RequestStack instance expected.');
}
if (null !== $context && !$context instanceof RequestContext) {
throw new \InvalidArgumentException('RequestContext instance expected.');
}
if (null !== $logger && !$logger instanceof LoggerInterface) {
throw new \InvalidArgumentException('Logger must implement LoggerInterface.');
}
if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
}
@@ -91,45 +71,27 @@ class RouterListener implements EventSubscriberInterface
$this->context = $context ?: $matcher->getContext();
$this->requestStack = $requestStack;
$this->logger = $logger;
}
/**
* Sets the current Request.
*
* This method was used to synchronize the Request, but as the HttpKernel
* is doing that automatically now, you should never call it directly.
* It is kept public for BC with the 2.3 version.
*
* @param Request|null $request A Request instance
*
* @deprecated since version 2.4, to be removed in 3.0.
*/
public function setRequest(Request $request = null)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be made private in 3.0.', E_USER_DEPRECATED);
$this->setCurrentRequest($request);
$this->projectDir = $projectDir;
$this->debug = $debug;
}
private function setCurrentRequest(Request $request = null)
{
if (null !== $request && $this->request !== $request) {
if (null !== $request) {
try {
$this->context->fromRequest($request);
} catch (\UnexpectedValueException $e) {
throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode());
}
}
$this->request = $request;
}
/**
* 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.
*/
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if (null === $this->requestStack) {
return; // removed when requestStack is required
}
$this->setCurrentRequest($this->requestStack->getParentRequest());
}
@@ -137,13 +99,7 @@ class RouterListener implements EventSubscriberInterface
{
$request = $event->getRequest();
// initialize the context that is also used by the generator (assuming matcher and generator share the same context instance)
// we call setCurrentRequest even if most of the time, it has already been done to keep compatibility
// with frameworks which do not use the Symfony service container
// when we have a RequestStack, no need to do it
if (null !== $this->requestStack) {
$this->setCurrentRequest($request);
}
$this->setCurrentRequest($request);
if ($request->attributes->has('_controller')) {
// routing is already done
@@ -160,10 +116,12 @@ class RouterListener implements EventSubscriberInterface
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Matched route "%s".', isset($parameters['_route']) ? $parameters['_route'] : 'n/a'), array(
$this->logger->info('Matched route "{route}".', [
'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a',
'route_parameters' => $parameters,
'request_uri' => $request->getUri(),
));
'method' => $request->getMethod(),
]);
}
$request->attributes->add($parameters);
@@ -184,11 +142,35 @@ class RouterListener implements EventSubscriberInterface
}
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (!$this->debug || !($e = $event->getException()) instanceof NotFoundHttpException) {
return;
}
if ($e->getPrevious() instanceof NoConfigurationException) {
$event->setResponse($this->createWelcomeResponse());
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
return [
KernelEvents::REQUEST => [['onKernelRequest', 32]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
KernelEvents::EXCEPTION => ['onKernelException', -64],
];
}
private function createWelcomeResponse()
{
$version = Kernel::VERSION;
$baseDir = realpath($this->projectDir).\DIRECTORY_SEPARATOR;
$docVersion = substr(Kernel::VERSION, 0, 3);
ob_start();
include __DIR__.'/../Resources/welcome.html.php';
return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND);
}
}

View File

@@ -29,7 +29,7 @@ use Symfony\Component\HttpKernel\KernelEvents;
* 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
* 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.
@@ -58,9 +58,9 @@ class SaveSessionListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
return [
// low priority but higher than StreamedResponseListener
KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)),
);
KernelEvents::RESPONSE => [['onKernelResponse', -1000]],
];
}
}

View File

@@ -11,44 +11,30 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Container\ContainerInterface;
/**
* Sets the session in the request.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.3
*/
abstract class SessionListener implements EventSubscriberInterface
class SessionListener extends AbstractSessionListener
{
public function onKernelRequest(GetResponseEvent $event)
private $container;
public function __construct(ContainerInterface $container)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $this->getSession();
if (null === $session || $request->hasSession()) {
return;
}
$request->setSession($session);
$this->container = $container;
}
public static function getSubscribedEvents()
protected function getSession()
{
return array(
KernelEvents::REQUEST => array('onKernelRequest', 128),
);
}
if (!$this->container->has('session')) {
return null;
}
/**
* Gets the session object.
*
* @return SessionInterface|null A SessionInterface instance or null if no session is available
*/
abstract protected function getSession();
return $this->container->get('session');
}
}

View File

@@ -42,8 +42,8 @@ class StreamedResponseListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -1024),
);
return [
KernelEvents::RESPONSE => ['onKernelResponse', -1024],
];
}
}

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -35,17 +36,30 @@ class SurrogateListener implements EventSubscriberInterface
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest() || null === $this->surrogate) {
if (!$event->isMasterRequest()) {
return;
}
$this->surrogate->addSurrogateControl($event->getResponse());
$kernel = $event->getKernel();
$surrogate = $this->surrogate;
if ($kernel instanceof HttpCache) {
$surrogate = $kernel->getSurrogate();
if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) {
$surrogate = $this->surrogate;
}
}
if (null === $surrogate) {
return;
}
$surrogate->addSurrogateControl($event->getResponse());
}
public static function getSubscribedEvents()
{
return array(
return [
KernelEvents::RESPONSE => 'onKernelResponse',
);
];
}
}

View File

@@ -11,72 +11,30 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Container\ContainerInterface;
/**
* TestSessionListener.
* Sets the session in the request.
*
* Saves session in test environment.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.3
*/
abstract class TestSessionListener implements EventSubscriberInterface
class TestSessionListener extends AbstractTestSessionListener
{
public function onKernelRequest(GetResponseEvent $event)
private $container;
public function __construct(ContainerInterface $container)
{
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()));
}
$this->container = $container;
}
/**
* Checks if session was initialized and saves if current request is master
* Runs on 'kernel.response' in test environment.
*/
public function onKernelResponse(FilterResponseEvent $event)
protected function getSession()
{
if (!$event->isMasterRequest()) {
return;
if (!$this->container->has('session')) {
return null;
}
$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']));
}
return $this->container->get('session');
}
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

@@ -51,11 +51,11 @@ class TranslatorListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
return [
// must be registered after the Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 10)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
KernelEvents::REQUEST => [['onKernelRequest', 10]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
];
}
private function setLocale(Request $request)

View File

@@ -16,8 +16,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Validates that the headers and other information indicating the
* client IP address of a request are consistent.
* Validates Requests.
*
* @author Magnus Nordlander <magnus@fervo.se>
*/
@@ -34,9 +33,10 @@ class ValidateRequestListener implements EventSubscriberInterface
$request = $event->getRequest();
if ($request::getTrustedProxies()) {
// This will throw an exception if the headers are inconsistent.
$request->getClientIps();
}
$request->getHost();
}
/**
@@ -44,10 +44,10 @@ class ValidateRequestListener implements EventSubscriberInterface
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(
array('onKernelRequest', 256),
),
);
return [
KernelEvents::REQUEST => [
['onKernelRequest', 256],
],
];
}
}

View File

@@ -24,6 +24,6 @@ class AccessDeniedHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(403, $message, $previous, array(), $code);
parent::__construct(403, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class BadRequestHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(400, $message, $previous, array(), $code);
parent::__construct(400, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class ConflictHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(409, $message, $previous, array(), $code);
parent::__construct(409, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class GoneHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(410, $message, $previous, array(), $code);
parent::__construct(410, $message, $previous, [], $code);
}
}

View File

@@ -21,7 +21,7 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface
private $statusCode;
private $headers;
public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0)
public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
@@ -38,4 +38,14 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface
{
return $this->headers;
}
/**
* Set response headers.
*
* @param array $headers Response headers
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;
}
}

View File

@@ -23,6 +23,6 @@ class LengthRequiredHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(411, $message, $previous, array(), $code);
parent::__construct(411, $message, $previous, [], $code);
}
}

View File

@@ -24,7 +24,7 @@ class MethodNotAllowedHttpException extends HttpException
*/
public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('Allow' => strtoupper(implode(', ', $allow)));
$headers = ['Allow' => strtoupper(implode(', ', $allow))];
parent::__construct(405, $message, $previous, $headers, $code);
}

View File

@@ -23,6 +23,6 @@ class NotAcceptableHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(406, $message, $previous, array(), $code);
parent::__construct(406, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class NotFoundHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(404, $message, $previous, array(), $code);
parent::__construct(404, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class PreconditionFailedHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(412, $message, $previous, array(), $code);
parent::__construct(412, $message, $previous, [], $code);
}
}

View File

@@ -25,6 +25,6 @@ class PreconditionRequiredHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(428, $message, $previous, array(), $code);
parent::__construct(428, $message, $previous, [], $code);
}
}

View File

@@ -24,9 +24,9 @@ class ServiceUnavailableHttpException extends HttpException
*/
public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array();
$headers = [];
if ($retryAfter) {
$headers = array('Retry-After' => $retryAfter);
$headers = ['Retry-After' => $retryAfter];
}
parent::__construct(503, $message, $previous, $headers, $code);

View File

@@ -26,9 +26,9 @@ class TooManyRequestsHttpException extends HttpException
*/
public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array();
$headers = [];
if ($retryAfter) {
$headers = array('Retry-After' => $retryAfter);
$headers = ['Retry-After' => $retryAfter];
}
parent::__construct(429, $message, $previous, $headers, $code);

View File

@@ -24,7 +24,7 @@ class UnauthorizedHttpException extends HttpException
*/
public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('WWW-Authenticate' => $challenge);
$headers = ['WWW-Authenticate' => $challenge];
parent::__construct(401, $message, $previous, $headers, $code);
}

View File

@@ -23,6 +23,6 @@ class UnprocessableEntityHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(422, $message, $previous, array(), $code);
parent::__construct(422, $message, $previous, [], $code);
}
}

View File

@@ -23,6 +23,6 @@ class UnsupportedMediaTypeHttpException extends HttpException
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(415, $message, $previous, array(), $code);
parent::__construct(415, $message, $previous, [], $code);
}
}

View File

@@ -59,9 +59,13 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
*
* @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface
*/
public function render($uri, Request $request, array $options = array())
public function render($uri, Request $request, array $options = [])
{
if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) {
if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) {
@trigger_error('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated since Symfony 3.1, and will be removed in 4.0. Use a different rendering strategy or pass scalar values.', E_USER_DEPRECATED);
}
return $this->inlineStrategy->render($uri, $request, $options);
}
@@ -90,4 +94,17 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
return substr($fragmentUri, \strlen($request->getSchemeAndHttpHost()));
}
private function containsNonScalars(array $values)
{
foreach ($values as $value) {
if (\is_array($value)) {
return $this->containsNonScalars($value);
} elseif (!is_scalar($value) && null !== $value) {
return true;
}
}
return false;
}
}

View File

@@ -11,7 +11,6 @@
namespace Symfony\Component\HttpKernel\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -23,11 +22,6 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference;
* This class handles the rendering of resource fragments that are included into
* a main resource. The handling of the rendering is managed by specialized renderers.
*
* This listener works in 2 modes:
*
* * 2.3 compatibility mode where you must call setRequest whenever the Request changes.
* * 2.4+ mode where you must pass a RequestStack instance in the constructor.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see FragmentRendererInterface
@@ -35,37 +29,16 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference;
class FragmentHandler
{
private $debug;
private $renderers = array();
private $request;
private $renderers = [];
private $requestStack;
/**
* RequestStack will become required in 3.0.
*
* @param RequestStack $requestStack The Request stack that controls the lifecycle of requests
* @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances
* @param bool $debug Whether the debug mode is enabled or not
*/
public function __construct($requestStack = null, $renderers = array(), $debug = false)
public function __construct(RequestStack $requestStack, array $renderers = [], $debug = false)
{
if (\is_array($requestStack)) {
$tmp = $debug;
$debug = \func_num_args() < 2 ? false : $renderers;
$renderers = $requestStack;
$requestStack = \func_num_args() < 3 ? null : $tmp;
@trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as first argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
} elseif (!$requestStack instanceof RequestStack) {
@trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED);
}
if (null !== $requestStack && !$requestStack instanceof RequestStack) {
throw new \InvalidArgumentException('RequestStack instance expected.');
}
if (!\is_array($renderers)) {
throw new \InvalidArgumentException('Renderers must be an array.');
}
$this->requestStack = $requestStack;
foreach ($renderers as $renderer) {
$this->addRenderer($renderer);
@@ -81,24 +54,6 @@ class FragmentHandler
$this->renderers[$renderer->getName()] = $renderer;
}
/**
* Sets the current Request.
*
* This method was used to synchronize the Request, but as the HttpKernel
* is doing that automatically now, you should never call it directly.
* It is kept public for BC with the 2.3 version.
*
* @param Request|null $request A Request instance
*
* @deprecated since version 2.4, to be removed in 3.0.
*/
public function setRequest(Request $request = null)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
$this->request = $request;
}
/**
* Renders a URI and returns the Response content.
*
@@ -115,7 +70,7 @@ class FragmentHandler
* @throws \InvalidArgumentException when the renderer does not exist
* @throws \LogicException when no master request is being handled
*/
public function render($uri, $renderer = 'inline', array $options = array())
public function render($uri, $renderer = 'inline', array $options = [])
{
if (!isset($options['ignore_errors'])) {
$options['ignore_errors'] = !$this->debug;
@@ -125,7 +80,7 @@ class FragmentHandler
throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
}
if (!$request = $this->getRequest()) {
if (!$request = $this->requestStack->getCurrentRequest()) {
throw new \LogicException('Rendering a fragment can only be done when handling a Request.');
}
@@ -145,7 +100,7 @@ class FragmentHandler
protected function deliver(Response $response)
{
if (!$response->isSuccessful()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->getRequest()->getUri(), $response->getStatusCode()));
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode()));
}
if (!$response instanceof StreamedResponse) {
@@ -153,10 +108,7 @@ class FragmentHandler
}
$response->sendContent();
}
private function getRequest()
{
return $this->requestStack ? $this->requestStack->getCurrentRequest() : $this->request;
return null;
}
}

View File

@@ -31,7 +31,7 @@ interface FragmentRendererInterface
*
* @return Response A Response instance
*/
public function render($uri, Request $request, array $options = array());
public function render($uri, Request $request, array $options = []);
/**
* Gets the name of the strategy.

View File

@@ -19,6 +19,7 @@ use Symfony\Component\Templating\EngineInterface;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Loader\ExistsLoaderInterface;
use Twig\Loader\SourceContextLoaderInterface;
/**
* Implements the Hinclude rendering strategy.
@@ -81,7 +82,7 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer
* * id: An optional hx:include tag id attribute
* * attributes: An optional array of hx:include tag attributes
*/
public function render($uri, Request $request, array $options = array())
public function render($uri, Request $request, array $options = [])
{
if ($uri instanceof ControllerReference) {
if (null === $this->signer) {
@@ -102,17 +103,13 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer
$content = $template;
}
$attributes = isset($options['attributes']) && \is_array($options['attributes']) ? $options['attributes'] : array();
$attributes = isset($options['attributes']) && \is_array($options['attributes']) ? $options['attributes'] : [];
if (isset($options['id']) && $options['id']) {
$attributes['id'] = $options['id'];
}
$renderedAttributes = '';
if (\count($attributes) > 0) {
if (\PHP_VERSION_ID >= 50400) {
$flags = ENT_QUOTES | ENT_SUBSTITUTE;
} else {
$flags = ENT_QUOTES;
}
$flags = ENT_QUOTES | ENT_SUBSTITUTE;
foreach ($attributes as $attribute => $value) {
$renderedAttributes .= sprintf(
' %s="%s"',
@@ -141,22 +138,23 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer
}
$loader = $this->templating->getLoader();
if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
return $loader->exists($template);
}
try {
if (method_exists($loader, 'getSourceContext')) {
$loader->getSourceContext($template);
} else {
$loader->getSource($template);
if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) {
try {
if ($loader instanceof SourceContextLoaderInterface) {
$loader->getSourceContext($template);
} else {
$loader->getSource($template);
}
return true;
} catch (LoaderError $e) {
}
return true;
} catch (LoaderError $e) {
return false;
}
return false;
return $loader->exists($template);
}
/**

View File

@@ -43,7 +43,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
*
* * alt: an alternative URI to render in case of an error
*/
public function render($uri, Request $request, array $options = array())
public function render($uri, Request $request, array $options = [])
{
$reference = null;
if ($uri instanceof ControllerReference) {
@@ -54,10 +54,10 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
// want that as we want to preserve objects (so we manually set Request attributes
// below instead)
$attributes = $reference->attributes;
$reference->attributes = array();
$reference->attributes = [];
// The request format and locale might have been overridden by the user
foreach (array('_format', '_locale') as $key) {
foreach (['_format', '_locale'] as $key) {
if (isset($attributes[$key])) {
$reference->attributes[$key] = $attributes[$key];
}
@@ -80,7 +80,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
// we dispatch the exception event to trigger the logging
// the response that comes back is simply ignored
// the response that comes back is ignored
if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) {
$event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e);
@@ -113,7 +113,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
unset($server['HTTP_IF_MODIFIED_SINCE']);
unset($server['HTTP_IF_NONE_MATCH']);
$subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server);
$subRequest = Request::create($uri, 'get', [], $cookies, [], $server);
if ($request->headers->has('Surrogate-Capability')) {
$subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability'));
}

View File

@@ -0,0 +1,136 @@
<?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\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Abstract class implementing Surrogate capabilities to Request and Response instances.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
abstract class AbstractSurrogate implements SurrogateInterface
{
protected $contentTypes;
protected $phpEscapeMap = [
['<?', '<%', '<s', '<S'],
['<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'],
];
/**
* @param array $contentTypes An array of content-type that should be parsed for Surrogate information
* (default: text/html, text/xml, application/xhtml+xml, and application/xml)
*/
public function __construct(array $contentTypes = ['text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'])
{
$this->contentTypes = $contentTypes;
}
/**
* Returns a new cache strategy instance.
*
* @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance
*/
public function createCacheStrategy()
{
return new ResponseCacheStrategy();
}
/**
* {@inheritdoc}
*/
public function hasSurrogateCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName())));
}
/**
* {@inheritdoc}
*/
public function addSurrogateCapability(Request $request)
{
$current = $request->headers->get('Surrogate-Capability');
$new = sprintf('symfony="%s/1.0"', strtoupper($this->getName()));
$request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new);
}
/**
* {@inheritdoc}
*/
public function needsParsing(Response $response)
{
if (!$control = $response->headers->get('Surrogate-Control')) {
return false;
}
$pattern = sprintf('#content="[^"]*%s/1.0[^"]*"#', strtoupper($this->getName()));
return (bool) preg_match($pattern, $control);
}
/**
* {@inheritdoc}
*/
public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors)
{
$subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all());
try {
$response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true);
if (!$response->isSuccessful()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode()));
}
return $response->getContent();
} catch (\Exception $e) {
if ($alt) {
return $this->handle($cache, $alt, '', $ignoreErrors);
}
if (!$ignoreErrors) {
throw $e;
}
}
return '';
}
/**
* Remove the Surrogate from the Surrogate-Control header.
*/
protected function removeFromControl(Response $response)
{
if (!$response->headers->has('Surrogate-Control')) {
return;
}
$value = $response->headers->get('Surrogate-Control');
$upperName = strtoupper($this->getName());
if (sprintf('content="%s/1.0"', $upperName) == $value) {
$response->headers->remove('Surrogate-Control');
} elseif (preg_match(sprintf('#,\s*content="%s/1.0"#', $upperName), $value)) {
$response->headers->set('Surrogate-Control', preg_replace(sprintf('#,\s*content="%s/1.0"#', $upperName), '', $value));
} elseif (preg_match(sprintf('#content="%s/1.0",\s*#', $upperName), $value)) {
$response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value));
}
}
}

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\HttpKernel\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Esi implements the ESI capabilities to Request and Response instances.
@@ -26,97 +25,15 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Esi implements SurrogateInterface
class Esi extends AbstractSurrogate
{
private $contentTypes;
private $phpEscapeMap = array(
array('<?', '<%', '<s', '<S'),
array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'),
);
/**
* @param array $contentTypes An array of content-type that should be parsed for ESI information
* (default: text/html, text/xml, application/xhtml+xml, and application/xml)
*/
public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'))
{
$this->contentTypes = $contentTypes;
}
public function getName()
{
return 'esi';
}
/**
* Returns a new cache strategy instance.
*
* @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance
*/
public function createCacheStrategy()
{
return new ResponseCacheStrategy();
}
/**
* Checks that at least one surrogate has ESI/1.0 capability.
*
* @return bool true if one surrogate has ESI/1.0 capability, false otherwise
*/
public function hasSurrogateCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, 'ESI/1.0');
}
/**
* Checks that at least one surrogate has ESI/1.0 capability.
*
* @param Request $request A Request instance
*
* @return bool true if one surrogate has ESI/1.0 capability, false otherwise
*
* @deprecated since version 2.6, to be removed in 3.0. Use hasSurrogateCapability() instead
*/
public function hasSurrogateEsiCapability(Request $request)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the hasSurrogateCapability() method instead.', E_USER_DEPRECATED);
return $this->hasSurrogateCapability($request);
}
/**
* Adds ESI/1.0 capability to the given Request.
*/
public function addSurrogateCapability(Request $request)
{
$current = $request->headers->get('Surrogate-Capability');
$new = 'symfony2="ESI/1.0"';
$request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new);
}
/**
* Adds ESI/1.0 capability to the given Request.
*
* @param Request $request A Request instance
*
* @deprecated since version 2.6, to be removed in 3.0. Use addSurrogateCapability() instead
*/
public function addSurrogateEsiCapability(Request $request)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the addSurrogateCapability() method instead.', E_USER_DEPRECATED);
$this->addSurrogateCapability($request);
}
/**
* Adds HTTP headers to specify that the Response needs to be parsed for ESI.
*
* This method only adds an ESI HTTP header if the Response has some ESI tags.
* {@inheritdoc}
*/
public function addSurrogateControl(Response $response)
{
@@ -126,44 +43,7 @@ class Esi implements SurrogateInterface
}
/**
* Checks that the Response needs to be parsed for ESI tags.
*
* @return bool true if the Response needs to be parsed, false otherwise
*/
public function needsParsing(Response $response)
{
if (!$control = $response->headers->get('Surrogate-Control')) {
return false;
}
return (bool) preg_match('#content="[^"]*ESI/1.0[^"]*"#', $control);
}
/**
* Checks that the Response needs to be parsed for ESI tags.
*
* @param Response $response A Response instance
*
* @return bool true if the Response needs to be parsed, false otherwise
*
* @deprecated since version 2.6, to be removed in 3.0. Use needsParsing() instead
*/
public function needsEsiParsing(Response $response)
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the needsParsing() method instead.', E_USER_DEPRECATED);
return $this->needsParsing($response);
}
/**
* Renders an ESI tag.
*
* @param string $uri A URI
* @param string $alt An alternate URI
* @param bool $ignoreErrors Whether to ignore errors or not
* @param string $comment A comment to add as an esi:include tag
*
* @return string
* {@inheritdoc}
*/
public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '')
{
@@ -181,9 +61,7 @@ class Esi implements SurrogateInterface
}
/**
* Replaces a Response ESI tags with the included resource content.
*
* @return Response
* {@inheritdoc}
*/
public function process(Request $request, Response $response)
{
@@ -207,7 +85,7 @@ class Esi implements SurrogateInterface
$i = 1;
while (isset($chunks[$i])) {
$options = array();
$options = [];
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
@@ -232,51 +110,8 @@ class Esi implements SurrogateInterface
$response->headers->set('X-Body-Eval', 'ESI');
// remove ESI/1.0 from the Surrogate-Control header
if ($response->headers->has('Surrogate-Control')) {
$value = $response->headers->get('Surrogate-Control');
if ('content="ESI/1.0"' == $value) {
$response->headers->remove('Surrogate-Control');
} elseif (preg_match('#,\s*content="ESI/1.0"#', $value)) {
$response->headers->set('Surrogate-Control', preg_replace('#,\s*content="ESI/1.0"#', '', $value));
} elseif (preg_match('#content="ESI/1.0",\s*#', $value)) {
$response->headers->set('Surrogate-Control', preg_replace('#content="ESI/1.0",\s*#', '', $value));
}
}
}
$this->removeFromControl($response);
/**
* Handles an ESI from the cache.
*
* @param HttpCache $cache An HttpCache instance
* @param string $uri The main URI
* @param string $alt An alternative URI
* @param bool $ignoreErrors Whether to ignore errors or not
*
* @return string
*
* @throws \RuntimeException
* @throws \Exception
*/
public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors)
{
$subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all());
try {
$response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true);
if (!$response->isSuccessful()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode()));
}
return $response->getContent();
} catch (\Exception $e) {
if ($alt) {
return $this->handle($cache, $alt, '', $ignoreErrors);
}
if (!$ignoreErrors) {
throw $e;
}
}
return $response;
}
}

View File

@@ -32,8 +32,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
private $request;
private $surrogate;
private $surrogateCacheStrategy;
private $options = array();
private $traces = array();
private $options = [];
private $traces = [];
/**
* Constructor.
@@ -70,24 +70,24 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
* This setting is overridden by the stale-if-error HTTP Cache-Control extension
* (see RFC 5861).
*/
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array())
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = [])
{
$this->store = $store;
$this->kernel = $kernel;
$this->surrogate = $surrogate;
// needed in case there is a fatal error because the backend is too slow to respond
register_shutdown_function(array($this->store, 'cleanup'));
register_shutdown_function([$this->store, 'cleanup']);
$this->options = array_merge(array(
$this->options = array_merge([
'debug' => false,
'default_ttl' => 0,
'private_headers' => array('Authorization', 'Cookie'),
'private_headers' => ['Authorization', 'Cookie'],
'allow_reload' => false,
'allow_revalidate' => false,
'stale_while_revalidate' => 2,
'stale_if_error' => 60,
), $options);
], $options);
}
/**
@@ -117,7 +117,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*/
public function getLog()
{
$log = array();
$log = [];
foreach ($this->traces as $request => $traces) {
$log[] = sprintf('%s: %s', $request, implode(', ', $traces));
}
@@ -154,29 +154,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*/
public function getSurrogate()
{
if (!$this->surrogate instanceof Esi) {
throw new \LogicException('This instance of HttpCache was not set up to use ESI as surrogate handler. You must overwrite and use createSurrogate');
}
return $this->surrogate;
}
/**
* Gets the Esi instance.
*
* @return Esi An Esi instance
*
* @throws \LogicException
*
* @deprecated since version 2.6, to be removed in 3.0. Use getSurrogate() instead
*/
public function getEsi()
{
@trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getSurrogate() method instead.', E_USER_DEPRECATED);
return $this->getSurrogate();
}
/**
* {@inheritdoc}
*/
@@ -184,7 +164,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
{
// FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->traces = array();
$this->traces = [];
// Keep a clone of the original request for surrogates so they can access it.
// We must clone here to get a separate instance because the application will modify the request during
// the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1
@@ -195,24 +175,25 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
}
}
$path = $request->getPathInfo();
if ($qs = $request->getQueryString()) {
$path .= '?'.$qs;
}
$this->traces[$request->getMethod().' '.$path] = array();
$this->traces[$this->getTraceKey($request)] = [];
if (!$request->isMethodSafe(false)) {
$response = $this->invalidate($request, $catch);
} elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) {
$response = $this->pass($request, $catch);
} elseif ($this->options['allow_reload'] && $request->isNoCache()) {
/*
If allow_reload is configured and the client requests "Cache-Control: no-cache",
reload the cache by fetching a fresh response and caching it (if possible).
*/
$this->record($request, 'reload');
$response = $this->fetch($request, $catch);
} else {
$response = $this->lookup($request, $catch);
}
$this->restoreResponseBody($request, $response);
$response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC')));
if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) {
$response->headers->set('X-Symfony-Cache', $this->getLog());
}
@@ -279,9 +260,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
$this->store->invalidate($request);
// As per the RFC, invalidate Location and Content-Location URLs if present
foreach (array('Location', 'Content-Location') as $header) {
foreach (['Location', 'Content-Location'] as $header) {
if ($uri = $response->headers->get($header)) {
$subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all());
$subRequest = Request::create($uri, 'get', [], [], [], $request->server->all());
$this->store->invalidate($subRequest);
}
@@ -318,13 +299,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*/
protected function lookup(Request $request, $catch = false)
{
// if allow_reload and no-cache Cache-Control, allow a cache reload
if ($this->options['allow_reload'] && $request->isNoCache()) {
$this->record($request, 'reload');
return $this->fetch($request, $catch);
}
try {
$entry = $this->store->lookup($request);
} catch (\Exception $e) {
@@ -378,12 +352,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
}
// add our cached last-modified validator
$subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified'));
if ($entry->headers->has('Last-Modified')) {
$subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified'));
}
// Add our cached etag validator to the environment.
// We keep the etags from the client to handle the case when the client
// has a different private valid entry which is not cached here.
$cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array();
$cachedEtags = $entry->getEtag() ? [$entry->getEtag()] : [];
$requestEtags = $request->getETags();
if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) {
$subRequest->headers->set('if_none_match', implode(', ', $etags));
@@ -403,7 +379,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
$entry = clone $entry;
$entry->headers->remove('Date');
foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) {
foreach (['Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified'] as $name) {
if ($response->headers->has($name)) {
$entry->headers->set($name, $response->headers->get($name));
}
@@ -422,9 +398,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
}
/**
* Forwards the Request to the backend and determines whether the response should be stored.
*
* This methods is triggered when the cache missed or a reload is required.
* Unconditionally fetches a fresh response from the backend and
* stores it in the cache if is cacheable.
*
* @param Request $request A Request instance
* @param bool $catch Whether to process exceptions
@@ -456,9 +431,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
/**
* Forwards the Request to the backend and returns the Response.
*
* @param Request $request A Request instance
* @param bool $catch Whether to catch exceptions or not
* @param Response $entry A Response instance (the stale entry if present, null otherwise)
* All backend requests (cache passes, fetches, cache validations)
* run through this method.
*
* @param bool $catch Whether to catch exceptions or not
* @param Response|null $entry A Response instance (the stale entry if present, null otherwise)
*
* @return Response A Response instance
*/
@@ -472,7 +449,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
$response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch);
// we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC
if (null !== $entry && \in_array($response->getStatusCode(), array(500, 502, 503, 504))) {
if (null !== $entry && \in_array($response->getStatusCode(), [500, 502, 503, 504])) {
if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) {
$age = $this->options['stale_if_error'];
}
@@ -484,6 +461,17 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
}
}
/*
RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate
clock MUST NOT send a "Date" header, although it MUST send one in most other cases
except for 1xx or 5xx responses where it MAY do so.
Anyway, a client that received a message without a "Date" header MUST add it.
*/
if (!$response->headers->has('Date')) {
$response->setDate(\DateTime::createFromFormat('U', time()));
}
$this->processResponseBody($request, $response);
if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
@@ -523,49 +511,39 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
// try to acquire a lock to call the backend
$lock = $this->store->lock($request);
if (true === $lock) {
// we have the lock, call the backend
return false;
}
// there is already another process calling the backend
if (true !== $lock) {
// check if we can serve the stale entry
if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) {
$age = $this->options['stale_while_revalidate'];
}
if (abs($entry->getTtl()) < $age) {
$this->record($request, 'stale-while-revalidate');
// server the stale response while there is a revalidation
return true;
}
// wait for the lock to be released
$wait = 0;
while ($this->store->isLocked($request) && $wait < 5000000) {
usleep(50000);
$wait += 50000;
}
if ($wait < 5000000) {
// replace the current entry with the fresh one
$new = $this->lookup($request);
$entry->headers = $new->headers;
$entry->setContent($new->getContent());
$entry->setStatusCode($new->getStatusCode());
$entry->setProtocolVersion($new->getProtocolVersion());
foreach ($new->headers->getCookies() as $cookie) {
$entry->headers->setCookie($cookie);
}
} else {
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
$entry->setStatusCode(503);
$entry->setContent('503 Service Unavailable');
$entry->headers->set('Retry-After', 10);
}
// May we serve a stale response?
if ($this->mayServeStaleWhileRevalidate($entry)) {
$this->record($request, 'stale-while-revalidate');
return true;
}
// we have the lock, call the backend
return false;
// wait for the lock to be released
if ($this->waitForLock($request)) {
// replace the current entry with the fresh one
$new = $this->lookup($request);
$entry->headers = $new->headers;
$entry->setContent($new->getContent());
$entry->setStatusCode($new->getStatusCode());
$entry->setProtocolVersion($new->getProtocolVersion());
foreach ($new->headers->getCookies() as $cookie) {
$entry->headers->setCookie($cookie);
}
} else {
// backend is slow as hell, send a 503 response (to avoid the dog pile effect)
$entry->setStatusCode(503);
$entry->setContent('503 Service Unavailable');
$entry->headers->set('Retry-After', 10);
}
return true;
}
/**
@@ -575,9 +553,6 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*/
protected function store(Request $request, Response $response)
{
if (!$response->headers->has('Date')) {
$response->setDate(\DateTime::createFromFormat('U', time()));
}
try {
$this->store->write($request, $response);
@@ -665,11 +640,57 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
* @param string $event The event name
*/
private function record(Request $request, $event)
{
$this->traces[$this->getTraceKey($request)][] = $event;
}
/**
* Calculates the key we use in the "trace" array for a given request.
*
* @return string
*/
private function getTraceKey(Request $request)
{
$path = $request->getPathInfo();
if ($qs = $request->getQueryString()) {
$path .= '?'.$qs;
}
$this->traces[$request->getMethod().' '.$path][] = $event;
return $request->getMethod().' '.$path;
}
/**
* Checks whether the given (cached) response may be served as "stale" when a revalidation
* is currently in progress.
*
* @return bool true when the stale response may be served, false otherwise
*/
private function mayServeStaleWhileRevalidate(Response $entry)
{
$timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate');
if (null === $timeout) {
$timeout = $this->options['stale_while_revalidate'];
}
return abs($entry->getTtl()) < $timeout;
}
/**
* Waits for the store to release a locked entry.
*
* @param Request $request The request to wait for
*
* @return bool true if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded
*/
private function waitForLock(Request $request)
{
$wait = 0;
while ($this->store->isLocked($request) && $wait < 100) {
usleep(50000);
++$wait;
}
return $wait < 100;
}
}

View File

@@ -5,10 +5,6 @@
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This code is partially based on the Rack-Cache library by Ryan Tomayko,
* which is released under the MIT license.
* (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801)
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
@@ -28,30 +24,69 @@ use Symfony\Component\HttpFoundation\Response;
*/
class ResponseCacheStrategy implements ResponseCacheStrategyInterface
{
private $cacheable = true;
/**
* Cache-Control headers that are sent to the final response if they appear in ANY of the responses.
*/
private static $overrideDirectives = ['private', 'no-cache', 'no-store', 'no-transform', 'must-revalidate', 'proxy-revalidate'];
/**
* Cache-Control headers that are sent to the final response if they appear in ALL of the responses.
*/
private static $inheritDirectives = ['public', 'immutable'];
private $embeddedResponses = 0;
private $ttls = array();
private $maxAges = array();
private $isNotCacheableResponseEmbedded = false;
private $age = 0;
private $flagDirectives = [
'no-cache' => null,
'no-store' => null,
'no-transform' => null,
'must-revalidate' => null,
'proxy-revalidate' => null,
'public' => null,
'private' => null,
'immutable' => null,
];
private $ageDirectives = [
'max-age' => null,
's-maxage' => null,
'expires' => null,
];
/**
* {@inheritdoc}
*/
public function add(Response $response)
{
if (!$response->isFresh() || !$response->isCacheable()) {
$this->cacheable = false;
} else {
$maxAge = $response->getMaxAge();
$this->ttls[] = $response->getTtl();
$this->maxAges[] = $maxAge;
++$this->embeddedResponses;
if (null === $maxAge) {
$this->isNotCacheableResponseEmbedded = true;
foreach (self::$overrideDirectives as $directive) {
if ($response->headers->hasCacheControlDirective($directive)) {
$this->flagDirectives[$directive] = true;
}
}
++$this->embeddedResponses;
foreach (self::$inheritDirectives as $directive) {
if (false !== $this->flagDirectives[$directive]) {
$this->flagDirectives[$directive] = $response->headers->hasCacheControlDirective($directive);
}
}
$age = $response->getAge();
$this->age = max($this->age, $age);
if ($this->willMakeFinalResponseUncacheable($response)) {
$this->isNotCacheableResponseEmbedded = true;
return;
}
$this->storeRelativeAgeDirective('max-age', $response->headers->getCacheControlDirective('max-age'), $age);
$this->storeRelativeAgeDirective('s-maxage', $response->headers->getCacheControlDirective('s-maxage') ?: $response->headers->getCacheControlDirective('max-age'), $age);
$expires = $response->getExpires();
$expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null;
$this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0);
}
/**
@@ -64,33 +99,123 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
return;
}
// Remove validation related headers in order to avoid browsers using
// their own cache, because some of the response content comes from
// at least one embedded response (which likely has a different caching strategy).
if ($response->isValidateable()) {
$response->setEtag(null);
$response->setLastModified(null);
}
// Remove validation related headers of the master response,
// because some of the response content comes from at least
// one embedded response (which likely has a different caching strategy).
$response->setEtag(null);
$response->setLastModified(null);
if (!$response->isFresh() || !$response->isCacheable()) {
$this->cacheable = false;
}
$this->add($response);
if (!$this->cacheable) {
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
$response->headers->set('Age', $this->age);
if ($this->isNotCacheableResponseEmbedded) {
$response->setExpires($response->getDate());
if ($this->flagDirectives['no-store']) {
$response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');
} else {
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
}
return;
}
$this->ttls[] = $response->getTtl();
$this->maxAges[] = $response->getMaxAge();
$flags = array_filter($this->flagDirectives);
if ($this->isNotCacheableResponseEmbedded) {
$response->headers->removeCacheControlDirective('s-maxage');
} elseif (null !== $maxAge = min($this->maxAges)) {
$response->setSharedMaxAge($maxAge);
$response->headers->set('Age', $maxAge - min($this->ttls));
if (isset($flags['must-revalidate'])) {
$flags['no-cache'] = true;
}
$response->headers->set('Cache-Control', implode(', ', array_keys($flags)));
$maxAge = null;
if (is_numeric($this->ageDirectives['max-age'])) {
$maxAge = $this->ageDirectives['max-age'] + $this->age;
$response->headers->addCacheControlDirective('max-age', $maxAge);
}
if (is_numeric($this->ageDirectives['s-maxage'])) {
$sMaxage = $this->ageDirectives['s-maxage'] + $this->age;
if ($maxAge !== $sMaxage) {
$response->headers->addCacheControlDirective('s-maxage', $sMaxage);
}
}
if (is_numeric($this->ageDirectives['expires'])) {
$date = clone $response->getDate();
$date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds');
$response->setExpires($date);
}
}
/**
* RFC2616, Section 13.4.
*
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
*
* @return bool
*/
private function willMakeFinalResponseUncacheable(Response $response)
{
// RFC2616: A response received with a status code of 200, 203, 300, 301 or 410
// MAY be stored by a cache […] unless a cache-control directive prohibits caching.
if ($response->headers->hasCacheControlDirective('no-cache')
|| $response->headers->getCacheControlDirective('no-store')
) {
return true;
}
// Last-Modified and Etag headers cannot be merged, they render the response uncacheable
// by default (except if the response also has max-age etc.).
if (\in_array($response->getStatusCode(), [200, 203, 300, 301, 410])
&& null === $response->getLastModified()
&& null === $response->getEtag()
) {
return false;
}
// RFC2616: A response received with any other status code (e.g. status codes 302 and 307)
// MUST NOT be returned in a reply to a subsequent request unless there are
// cache-control directives or another header(s) that explicitly allow it.
$cacheControl = ['max-age', 's-maxage', 'must-revalidate', 'proxy-revalidate', 'public', 'private'];
foreach ($cacheControl as $key) {
if ($response->headers->hasCacheControlDirective($key)) {
return false;
}
}
if ($response->headers->has('Expires')) {
return false;
}
return true;
}
/**
* Store lowest max-age/s-maxage/expires for the final response.
*
* The response might have been stored in cache a while ago. To keep things comparable,
* we have to subtract the age so that the value is normalized for an age of 0.
*
* If the value is lower than the currently stored value, we update the value, to keep a rolling
* minimal value of each instruction. If the value is NULL, the directive will not be set on the final response.
*
* @param string $directive
* @param int|null $value
* @param int $age
*/
private function storeRelativeAgeDirective($directive, $value, $age)
{
if (null === $value) {
$this->ageDirectives[$directive] = false;
}
if (false !== $this->ageDirectives[$directive]) {
$value -= $age;
$this->ageDirectives[$directive] = null !== $this->ageDirectives[$directive] ? min($this->ageDirectives[$directive], $value) : $value;
}
$response->setMaxAge(0);
}
}

View File

@@ -13,30 +13,14 @@ namespace Symfony\Component\HttpKernel\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Ssi implements the SSI capabilities to Request and Response instances.
*
* @author Sebastian Krebs <krebs.seb@gmail.com>
*/
class Ssi implements SurrogateInterface
class Ssi extends AbstractSurrogate
{
private $contentTypes;
private $phpEscapeMap = array(
array('<?', '<%', '<s', '<S'),
array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'),
);
/**
* @param array $contentTypes An array of content-type that should be parsed for SSI information
* (default: text/html, text/xml, application/xhtml+xml, and application/xml)
*/
public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'))
{
$this->contentTypes = $contentTypes;
}
/**
* {@inheritdoc}
*/
@@ -45,37 +29,6 @@ class Ssi implements SurrogateInterface
return 'ssi';
}
/**
* {@inheritdoc}
*/
public function createCacheStrategy()
{
return new ResponseCacheStrategy();
}
/**
* {@inheritdoc}
*/
public function hasSurrogateCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, 'SSI/1.0');
}
/**
* {@inheritdoc}
*/
public function addSurrogateCapability(Request $request)
{
$current = $request->headers->get('Surrogate-Capability');
$new = 'symfony2="SSI/1.0"';
$request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new);
}
/**
* {@inheritdoc}
*/
@@ -86,18 +39,6 @@ class Ssi implements SurrogateInterface
}
}
/**
* {@inheritdoc}
*/
public function needsParsing(Response $response)
{
if (!$control = $response->headers->get('Surrogate-Control')) {
return false;
}
return (bool) preg_match('#content="[^"]*SSI/1.0[^"]*"#', $control);
}
/**
* {@inheritdoc}
*/
@@ -129,7 +70,7 @@ class Ssi implements SurrogateInterface
$i = 1;
while (isset($chunks[$i])) {
$options = array();
$options = [];
preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
@@ -152,41 +93,8 @@ class Ssi implements SurrogateInterface
$response->headers->set('X-Body-Eval', 'SSI');
// remove SSI/1.0 from the Surrogate-Control header
if ($response->headers->has('Surrogate-Control')) {
$value = $response->headers->get('Surrogate-Control');
if ('content="SSI/1.0"' == $value) {
$response->headers->remove('Surrogate-Control');
} elseif (preg_match('#,\s*content="SSI/1.0"#', $value)) {
$response->headers->set('Surrogate-Control', preg_replace('#,\s*content="SSI/1.0"#', '', $value));
} elseif (preg_match('#content="SSI/1.0",\s*#', $value)) {
$response->headers->set('Surrogate-Control', preg_replace('#content="SSI/1.0",\s*#', '', $value));
}
}
}
$this->removeFromControl($response);
/**
* {@inheritdoc}
*/
public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors)
{
$subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all());
try {
$response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true);
if (!$response->isSuccessful()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode()));
}
return $response->getContent();
} catch (\Exception $e) {
if ($alt) {
return $this->handle($cache, $alt, '', $ignoreErrors);
}
if (!$ignoreErrors) {
throw $e;
}
}
return $response;
}
}

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