Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions
web/vendor/symfony/http-kernel
.gitignore
Bundle
CHANGELOG.md
CacheClearer
CacheWarmer
Client.php
Config
Controller
DataCollector
Debug
DependencyInjection
Event
EventListener
Exception
Fragment
HttpCache

View file

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

View file

@ -0,0 +1,233 @@
<?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\Bundle;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Console\Application;
use Symfony\Component\Finder\Finder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
* An implementation of BundleInterface that adds a few conventions
* for DependencyInjection extensions and Console commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Bundle implements BundleInterface
{
/**
* @var ContainerInterface
*/
protected $container;
protected $name;
protected $extension;
protected $path;
/**
* Boots the Bundle.
*/
public function boot()
{
}
/**
* Shutdowns the Bundle.
*/
public function shutdown()
{
}
/**
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
*
* This method can be overridden to register compilation passes,
* other extensions, ...
*
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function build(ContainerBuilder $container)
{
}
/**
* Sets the container.
*
* @param ContainerInterface|null $container A ContainerInterface instance or null
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* Returns the bundle's container extension.
*
* @return ExtensionInterface|null The container extension
*
* @throws \LogicException
*/
public function getContainerExtension()
{
if (null === $this->extension) {
$extension = $this->createContainerExtension();
if (null !== $extension) {
if (!$extension instanceof ExtensionInterface) {
throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_class($extension)));
}
// check naming convention
$basename = preg_replace('/Bundle$/', '', $this->getName());
$expectedAlias = Container::underscore($basename);
if ($expectedAlias != $extension->getAlias()) {
throw new \LogicException(sprintf(
'Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.',
$expectedAlias, $extension->getAlias()
));
}
$this->extension = $extension;
} else {
$this->extension = false;
}
}
if ($this->extension) {
return $this->extension;
}
}
/**
* Gets the Bundle namespace.
*
* @return string The Bundle namespace
*/
public function getNamespace()
{
$class = get_class($this);
return substr($class, 0, strrpos($class, '\\'));
}
/**
* Gets the Bundle directory path.
*
* @return string The Bundle absolute path
*/
public function getPath()
{
if (null === $this->path) {
$reflected = new \ReflectionObject($this);
$this->path = dirname($reflected->getFileName());
}
return $this->path;
}
/**
* Returns the bundle parent name.
*
* @return string The Bundle parent name it overrides or null if no parent
*/
public function getParent()
{
}
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*/
final public function getName()
{
if (null !== $this->name) {
return $this->name;
}
$name = get_class($this);
$pos = strrpos($name, '\\');
return $this->name = false === $pos ? $name : substr($name, $pos + 1);
}
/**
* Finds and registers Commands.
*
* Override this method if your bundle commands do not follow the conventions:
*
* * Commands are in the 'Command' sub-directory
* * Commands extend Symfony\Component\Console\Command\Command
*
* @param Application $application An Application instance
*/
public function registerCommands(Application $application)
{
if (!is_dir($dir = $this->getPath().'/Command')) {
return;
}
if (!class_exists('Symfony\Component\Finder\Finder')) {
throw new \RuntimeException('You need the symfony/finder component to register bundle commands.');
}
$finder = new Finder();
$finder->files()->name('*Command.php')->in($dir);
$prefix = $this->getNamespace().'\\Command';
foreach ($finder as $file) {
$ns = $prefix;
if ($relativePath = $file->getRelativePath()) {
$ns .= '\\'.str_replace('/', '\\', $relativePath);
}
$class = $ns.'\\'.$file->getBasename('.php');
if ($this->container) {
$alias = 'console.command.'.strtolower(str_replace('\\', '_', $class));
if ($this->container->has($alias)) {
continue;
}
}
$r = new \ReflectionClass($class);
if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) {
$application->add($r->newInstance());
}
}
}
/**
* Returns the bundle's container extension class.
*
* @return string
*/
protected function getContainerExtensionClass()
{
$basename = preg_replace('/Bundle$/', '', $this->getName());
return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension';
}
/**
* Creates the bundle's container extension.
*
* @return ExtensionInterface|null
*/
protected function createContainerExtension()
{
if (class_exists($class = $this->getContainerExtensionClass())) {
return new $class();
}
}
}

View file

@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Bundle;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
* BundleInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface BundleInterface extends ContainerAwareInterface
{
/**
* Boots the Bundle.
*/
public function boot();
/**
* Shutdowns the Bundle.
*/
public function shutdown();
/**
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
*
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function build(ContainerBuilder $container);
/**
* Returns the container extension that should be implicitly loaded.
*
* @return ExtensionInterface|null The default extension or null if there is none
*/
public function getContainerExtension();
/**
* Returns the bundle name that this bundle overrides.
*
* Despite its name, this method does not imply any parent/child relationship
* between the bundles, just a way to extend and override an existing
* bundle.
*
* @return string The Bundle name it overrides or null if no parent
*/
public function getParent();
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*/
public function getName();
/**
* Gets the Bundle namespace.
*
* @return string The Bundle namespace
*/
public function getNamespace();
/**
* Gets the Bundle directory path.
*
* The path should always be returned as a Unix path (with /).
*
* @return string The Bundle absolute path
*/
public function getPath();
}

View file

@ -0,0 +1,78 @@
CHANGELOG
=========
2.8.0
-----
* deprecated `Profiler::import` and `Profiler::export`
2.7.0
-----
* added the HTTP status code to profiles
2.6.0
-----
* deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead
* deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle`
2.5.0
-----
* deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead
2.4.0
-----
* added event listeners for the session
* added the KernelEvents::FINISH_REQUEST event
2.3.0
-----
* [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor
* deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`,
`Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException`
* deprecated `Symfony\Component\HttpKernel\Kernel::init()``
* added the possibility to specify an id an extra attributes to hinclude tags
* added the collect of data if a controller is a Closure in the Request collector
* pass exceptions from the ExceptionListener to the logger using the logging context to allow for more
detailed messages
2.2.0
-----
* [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default)
* added Symfony\Component\HttpKernel\EventListener\FragmentListener
* added Symfony\Component\HttpKernel\UriSigner
* added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface)
* added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel
* added ControllerReference to create reference of Controllers (used in the FragmentRenderer class)
* [BC BREAK] renamed TimeDataCollector::getTotalTime() to
TimeDataCollector::getDuration()
* updated the MemoryDataCollector to include the memory used in the
kernel.terminate event listeners
* moved the Stopwatch classes to a new component
* added TraceableControllerResolver
* added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher)
* added support for WinCache opcode cache in ConfigDataCollector
2.1.0
-----
* [BC BREAK] the charset is now configured via the Kernel::getCharset() method
* [BC BREAK] the current locale for the user is not stored anymore in the session
* added the HTTP method to the profiler storage
* updated all listeners to implement EventSubscriberInterface
* added TimeDataCollector
* added ContainerAwareTraceableEventDispatcher
* moved TraceableEventDispatcherInterface to the EventDispatcher component
* added RouterListener, LocaleListener, and StreamedResponseListener
* added CacheClearerInterface (and ChainCacheClearer)
* added a kernel.terminate event (via TerminableInterface and PostResponseEvent)
* added a Stopwatch class
* added WarmableInterface
* improved extensibility between bundles
* added profiler storages for Memcache(d), File-based, MongoDB, Redis
* moved Filesystem class to its own component

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\CacheClearer;
/**
* CacheClearerInterface.
*
* @author Dustin Dobervich <ddobervich@gmail.com>
*/
interface CacheClearerInterface
{
/**
* Clears any caches necessary.
*
* @param string $cacheDir The cache directory
*/
public function clear($cacheDir);
}

View file

@ -0,0 +1,55 @@
<?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;
/**
* ChainCacheClearer.
*
* @author Dustin Dobervich <ddobervich@gmail.com>
*/
class ChainCacheClearer implements CacheClearerInterface
{
/**
* @var array
*/
protected $clearers;
/**
* Constructs a new instance of ChainCacheClearer.
*
* @param array $clearers The initial clearers
*/
public function __construct(array $clearers = array())
{
$this->clearers = $clearers;
}
/**
* {@inheritdoc}
*/
public function clear($cacheDir)
{
foreach ($this->clearers as $clearer) {
$clearer->clear($cacheDir);
}
}
/**
* Adds a cache clearer to the aggregate.
*
* @param CacheClearerInterface $clearer
*/
public function add(CacheClearerInterface $clearer)
{
$this->clearers[] = $clearer;
}
}

View file

@ -0,0 +1,32 @@
<?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\CacheWarmer;
/**
* Abstract cache warmer that knows how to write a file to the cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class CacheWarmer implements CacheWarmerInterface
{
protected function writeCacheFile($file, $content)
{
$tmpFile = @tempnam(dirname($file), basename($file));
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
@chmod($file, 0666 & ~umask());
return;
}
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
}
}

View file

@ -0,0 +1,74 @@
<?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\CacheWarmer;
/**
* Aggregates several cache warmers into a single one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CacheWarmerAggregate implements CacheWarmerInterface
{
protected $warmers = array();
protected $optionalsEnabled = false;
public function __construct(array $warmers = array())
{
foreach ($warmers as $warmer) {
$this->add($warmer);
}
}
public function enableOptionalWarmers()
{
$this->optionalsEnabled = true;
}
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
*/
public function warmUp($cacheDir)
{
foreach ($this->warmers as $warmer) {
if (!$this->optionalsEnabled && $warmer->isOptional()) {
continue;
}
$warmer->warmUp($cacheDir);
}
}
/**
* Checks whether this warmer is optional or not.
*
* @return bool always false
*/
public function isOptional()
{
return false;
}
public function setWarmers(array $warmers)
{
$this->warmers = array();
foreach ($warmers as $warmer) {
$this->add($warmer);
}
}
public function add(CacheWarmerInterface $warmer)
{
$this->warmers[] = $warmer;
}
}

View file

@ -0,0 +1,32 @@
<?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\CacheWarmer;
/**
* Interface for classes able to warm up the cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface CacheWarmerInterface extends WarmableInterface
{
/**
* Checks whether this warmer is optional or not.
*
* Optional warmers can be ignored on certain conditions.
*
* A warmer should return true if the cache can be
* generated incrementally and on-demand.
*
* @return bool true if the warmer is optional, false otherwise
*/
public function isOptional();
}

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\CacheWarmer;
/**
* Interface for classes that support warming their cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface WarmableInterface
{
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
*/
public function warmUp($cacheDir);
}

View file

@ -0,0 +1,226 @@
<?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;
use Symfony\Component\BrowserKit\Client as BaseClient;
use Symfony\Component\BrowserKit\Request as DomRequest;
use Symfony\Component\BrowserKit\Response as DomResponse;
use Symfony\Component\BrowserKit\Cookie as DomCookie;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Client simulates a browser and makes requests to a Kernel object.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Client extends BaseClient
{
protected $kernel;
/**
* Constructor.
*
* @param HttpKernelInterface $kernel An HttpKernel instance
* @param array $server The server parameters (equivalent of $_SERVER)
* @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)
{
// These class properties must be set before calling the parent constructor, as it may depend on it.
$this->kernel = $kernel;
$this->followRedirects = false;
parent::__construct($server, $history, $cookieJar);
}
/**
* {@inheritdoc}
*
* @return Request|null A Request instance
*/
public function getRequest()
{
return parent::getRequest();
}
/**
* {@inheritdoc}
*
* @return Response|null A Response instance
*/
public function getResponse()
{
return parent::getResponse();
}
/**
* Makes a request.
*
* @param Request $request A Request instance
*
* @return Response A Response instance
*/
protected function doRequest($request)
{
$response = $this->kernel->handle($request);
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($request, $response);
}
return $response;
}
/**
* Returns the script to execute when the request must be insulated.
*
* @param Request $request A Request instance
*
* @return string
*/
protected function getScript($request)
{
$kernel = str_replace("'", "\\'", serialize($this->kernel));
$request = str_replace("'", "\\'", serialize($request));
$r = new \ReflectionClass('\\Symfony\\Component\\ClassLoader\\ClassLoader');
$requirePath = str_replace("'", "\\'", $r->getFileName());
$symfonyPath = str_replace("'", "\\'", dirname(dirname(dirname(__DIR__))));
$errorReporting = error_reporting();
$code = <<<EOF
<?php
error_reporting($errorReporting);
require_once '$requirePath';
\$loader = new Symfony\Component\ClassLoader\ClassLoader();
\$loader->addPrefix('Symfony', '$symfonyPath');
\$loader->register();
\$kernel = unserialize('$kernel');
\$request = unserialize('$request');
EOF;
return $code.$this->getHandleScript();
}
protected function getHandleScript()
{
return <<<'EOF'
$response = $kernel->handle($request);
if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) {
$kernel->terminate($request, $response);
}
echo serialize($response);
EOF;
}
/**
* Converts the BrowserKit request to a HttpKernel request.
*
* @param DomRequest $request A DomRequest instance
*
* @return Request A Request instance
*/
protected function filterRequest(DomRequest $request)
{
$httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent());
foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) {
$httpRequest->files->set($key, $value);
}
return $httpRequest;
}
/**
* Filters an array of files.
*
* This method created test instances of UploadedFile so that the move()
* method can be called on those instances.
*
* If the size of a file is greater than the allowed size (from php.ini) then
* an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE.
*
* @see UploadedFile
*
* @param array $files An array of files
*
* @return array An array with all uploaded files marked as already moved
*/
protected function filterFiles(array $files)
{
$filtered = array();
foreach ($files as $key => $value) {
if (is_array($value)) {
$filtered[$key] = $this->filterFiles($value);
} elseif ($value instanceof UploadedFile) {
if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) {
$filtered[$key] = new UploadedFile(
'',
$value->getClientOriginalName(),
$value->getClientMimeType(),
0,
UPLOAD_ERR_INI_SIZE,
true
);
} else {
$filtered[$key] = new UploadedFile(
$value->getPathname(),
$value->getClientOriginalName(),
$value->getClientMimeType(),
$value->getClientSize(),
$value->getError(),
true
);
}
}
}
return $filtered;
}
/**
* Converts the HttpKernel response to a BrowserKit response.
*
* @param Response $response A Response instance
*
* @return DomResponse A DomResponse instance
*/
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);
}
}

View file

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

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Config;
use Symfony\Component\Config\FileLocator as BaseFileLocator;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* FileLocator uses the KernelInterface to locate resources in bundles.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLocator extends BaseFileLocator
{
private $kernel;
private $path;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param null|string $path The path the global resource directory
* @param array $paths An array of paths where to look for resources
*/
public function __construct(KernelInterface $kernel, $path = null, array $paths = array())
{
$this->kernel = $kernel;
if (null !== $path) {
$this->path = $path;
$paths[] = $path;
}
parent::__construct($paths);
}
/**
* {@inheritdoc}
*/
public function locate($file, $currentPath = null, $first = true)
{
if (isset($file[0]) && '@' === $file[0]) {
return $this->kernel->locateResource($file, $this->path, $first);
}
return parent::locate($file, $currentPath, $first);
}
}

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;
use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
/**
* Acts as a marker and a data holder for a Controller.
*
* Some methods in Symfony accept both a URI (as a string) or a controller as
* an argument. In the latter case, instead of passing an array representing
* the controller, you can use an instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see FragmentRendererInterface
*/
class ControllerReference
{
public $controller;
public $attributes = array();
public $query = array();
/**
* Constructor.
*
* @param string $controller The controller name
* @param array $attributes An array of parameters to add to the Request attributes
* @param array $query An array of parameters to add to the Request query string
*/
public function __construct($controller, array $attributes = array(), array $query = array())
{
$this->controller = $controller;
$this->attributes = $attributes;
$this->query = $query;
}
}

View file

@ -0,0 +1,196 @@
<?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\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* ControllerResolver.
*
* This implementation uses the '_controller' request attribute to determine
* the controller to execute and uses the request attributes to determine
* the controller method arguments.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ControllerResolver implements ControllerResolverInterface
{
private $logger;
/**
* If the ...$arg functionality is available.
*
* Requires at least PHP 5.6.0 or HHVM 3.9.1
*
* @var bool
*/
private $supportsVariadic;
/**
* If scalar types exists.
*
* @var bool
*/
private $supportsScalarTypes;
/**
* Constructor.
*
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
$this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType');
}
/**
* {@inheritdoc}
*
* This method looks for a '_controller' request attribute that represents
* the controller name (a string like ClassName::MethodName).
*/
public function getController(Request $request)
{
if (!$controller = $request->attributes->get('_controller')) {
if (null !== $this->logger) {
$this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.');
}
return false;
}
if (is_array($controller)) {
return $controller;
}
if (is_object($controller)) {
if (method_exists($controller, '__invoke')) {
return $controller;
}
throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo()));
}
if (false === strpos($controller, ':')) {
if (method_exists($controller, '__invoke')) {
return $this->instantiateController($controller);
} elseif (function_exists($controller)) {
return $controller;
}
}
$callable = $this->createController($controller);
if (!is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', $controller, $request->getPathInfo()));
}
return $callable;
}
/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
{
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && !$controller instanceof \Closure) {
$r = new \ReflectionObject($controller);
$r = $r->getMethod('__invoke');
} else {
$r = new \ReflectionFunction($controller);
}
return $this->doGetArguments($request, $controller, $r->getParameters());
}
/**
* @param Request $request
* @param callable $controller
* @param \ReflectionParameter[] $parameters
*
* @return array The arguments to use when calling the action
*/
protected function doGetArguments(Request $request, $controller, array $parameters)
{
$attributes = $request->attributes->all();
$arguments = array();
foreach ($parameters as $param) {
if (array_key_exists($param->name, $attributes)) {
if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) {
$arguments = array_merge($arguments, array_values($attributes[$param->name]));
} else {
$arguments[] = $attributes[$param->name];
}
} elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
$arguments[] = $request;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
} elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) {
$arguments[] = null;
} else {
if (is_array($controller)) {
$repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);
} elseif (is_object($controller)) {
$repr = get_class($controller);
} else {
$repr = $controller;
}
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name));
}
}
return $arguments;
}
/**
* Returns a callable for the given controller.
*
* @param string $controller A Controller string
*
* @return callable A PHP callable
*
* @throws \InvalidArgumentException
*/
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
}
list($class, $method) = explode('::', $controller, 2);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
return array($this->instantiateController($class), $method);
}
/**
* Returns an instantiated controller.
*
* @param string $class A class name
*
* @return object
*/
protected function instantiateController($class)
{
return new $class();
}
}

View file

@ -0,0 +1,57 @@
<?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;
/**
* A ControllerResolverInterface implementation knows how to determine the
* controller to execute based on a Request object.
*
* It can also determine the arguments to pass to the Controller.
*
* A Controller can be any valid PHP callable.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ControllerResolverInterface
{
/**
* Returns the Controller instance associated with a Request.
*
* 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
* controller but cannot because of some errors made by the developer.
*
* @param Request $request A Request instance
*
* @return callable|false A PHP callable representing the Controller,
* or false if this resolver is not able to determine the controller
*
* @throws \LogicException If the controller can't be found
*/
public function getController(Request $request);
/**
* Returns the arguments to pass to the controller.
*
* @param Request $request A Request instance
* @param callable $controller A PHP callable
*
* @return array An array of arguments to pass to the controller
*
* @throws \RuntimeException When value for argument given is not provided
*/
public function getArguments(Request $request, $controller);
}

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\Controller;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpFoundation\Request;
/**
* TraceableControllerResolver.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableControllerResolver implements ControllerResolverInterface
{
private $resolver;
private $stopwatch;
/**
* Constructor.
*
* @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
*/
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch)
{
$this->resolver = $resolver;
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function getController(Request $request)
{
$e = $this->stopwatch->start('controller.get_callable');
$ret = $this->resolver->getController($request);
$e->stop();
return $ret;
}
/**
* {@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

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* AjaxDataCollector.
*
* @author Bart van den Burg <bart@burgov.nl>
*/
class AjaxDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception = null)
{
// all collecting is done client side
}
public function getName()
{
return 'ajax';
}
}

View file

@ -0,0 +1,291 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* ConfigDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfigDataCollector extends DataCollector
{
/**
* @var KernelInterface
*/
private $kernel;
private $name;
private $version;
private $cacheVersionInfo = true;
/**
* Constructor.
*
* @param string $name The name of the application using the web profiler
* @param string $version The version of the application using the web profiler
*/
public function __construct($name = null, $version = null)
{
$this->name = $name;
$this->version = $version;
}
/**
* Sets the Kernel associated with this Request.
*
* @param KernelInterface $kernel A KernelInterface instance
*/
public function setKernel(KernelInterface $kernel = null)
{
$this->kernel = $kernel;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'app_name' => $this->name,
'app_version' => $this->version,
'token' => $response->headers->get('X-Debug-Token'),
'symfony_version' => Kernel::VERSION,
'symfony_state' => 'unknown',
'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a',
'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a',
'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a',
'php_version' => PHP_VERSION,
'xdebug_enabled' => extension_loaded('xdebug'),
'eaccel_enabled' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'),
'apc_enabled' => extension_loaded('apc') && ini_get('apc.enabled'),
'xcache_enabled' => extension_loaded('xcache') && ini_get('xcache.cacher'),
'wincache_enabled' => extension_loaded('wincache') && ini_get('wincache.ocenabled'),
'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'),
'bundles' => array(),
'sapi_name' => PHP_SAPI,
);
if (isset($this->kernel)) {
foreach ($this->kernel->getBundles() as $name => $bundle) {
$this->data['bundles'][$name] = $bundle->getPath();
}
$this->data['symfony_state'] = $this->determineSymfonyState();
}
}
public function getApplicationName()
{
return $this->data['app_name'];
}
public function getApplicationVersion()
{
return $this->data['app_version'];
}
/**
* Gets the token.
*
* @return string The token
*/
public function getToken()
{
return $this->data['token'];
}
/**
* Gets the Symfony version.
*
* @return string The Symfony version
*/
public function getSymfonyVersion()
{
return $this->data['symfony_version'];
}
/**
* Returns the state of the current Symfony release.
*
* @return string One of: unknown, dev, stable, eom, eol
*/
public function getSymfonyState()
{
return $this->data['symfony_state'];
}
public function setCacheVersionInfo($cacheVersionInfo)
{
$this->cacheVersionInfo = $cacheVersionInfo;
}
/**
* Gets the PHP version.
*
* @return string The PHP version
*/
public function getPhpVersion()
{
return $this->data['php_version'];
}
/**
* Gets the application name.
*
* @return string The application name
*/
public function getAppName()
{
return $this->data['name'];
}
/**
* Gets the environment.
*
* @return string The environment
*/
public function getEnv()
{
return $this->data['env'];
}
/**
* Returns true if the debug is enabled.
*
* @return bool true if debug is enabled, false otherwise
*/
public function isDebug()
{
return $this->data['debug'];
}
/**
* Returns true if the XDebug is enabled.
*
* @return bool true if XDebug is enabled, false otherwise
*/
public function hasXDebug()
{
return $this->data['xdebug_enabled'];
}
/**
* Returns true if EAccelerator is enabled.
*
* @return bool true if EAccelerator is enabled, false otherwise
*/
public function hasEAccelerator()
{
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'];
}
/**
* Returns true if Zend OPcache is enabled.
*
* @return bool true if Zend OPcache is enabled, false otherwise
*/
public function hasZendOpcache()
{
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'];
}
/**
* Gets the PHP SAPI name.
*
* @return string The environment
*/
public function getSapiName()
{
return $this->data['sapi_name'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'config';
}
/**
* Tries to retrieve information about the current Symfony version.
*
* @return string One of: dev, stable, eom, eol
*/
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');
if ($now > $eol) {
$versionState = 'eol';
} elseif ($now > $eom) {
$versionState = 'eom';
} elseif ('' !== Kernel::EXTRA_VERSION) {
$versionState = 'dev';
} else {
$versionState = 'stable';
}
return $versionState;
}
}

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\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
/**
* DataCollector.
*
* Children of this class must store the collected data in the data property.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@symfony.com>
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
{
protected $data = array();
/**
* @var ValueExporter
*/
private $valueExporter;
public function serialize()
{
return serialize($this->data);
}
public function unserialize($data)
{
$this->data = unserialize($data);
}
/**
* Converts a PHP variable to a string.
*
* @param mixed $var A PHP variable
*
* @return string The string representation of the variable
*/
protected function varToString($var)
{
if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter();
}
return $this->valueExporter->exportValue($var);
}
}

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\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* DataCollectorInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface DataCollectorInterface
{
/**
* Collects data for the given Request and Response.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
* @param \Exception $exception An Exception instance
*/
public function collect(Request $request, Response $response, \Exception $exception = null);
/**
* Returns the name of the collector.
*
* @return string The collector name
*/
public function getName();
}

View file

@ -0,0 +1,310 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpDataCollector extends DataCollector implements DataDumperInterface
{
private $stopwatch;
private $fileLinkFormat;
private $dataCount = 0;
private $isCollected = true;
private $clonesCount = 0;
private $clonesIndex = 0;
private $rootRefs;
private $charset;
private $requestStack;
private $dumper;
private $dumperIsInjected;
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null)
{
$this->stopwatch = $stopwatch;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8';
$this->requestStack = $requestStack;
$this->dumper = $dumper;
$this->dumperIsInjected = null !== $dumper;
// All clones share these properties by reference:
$this->rootRefs = array(
&$this->data,
&$this->dataCount,
&$this->isCollected,
&$this->clonesCount,
);
}
public function __clone()
{
$this->clonesIndex = ++$this->clonesCount;
}
public function dump(Data $data)
{
if ($this->stopwatch) {
$this->stopwatch->start('dump');
}
if ($this->isCollected) {
$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);
}
$file = $trace[0]['file'];
$line = $trace[0]['line'];
$name = false;
$fileExcerpt = false;
for ($i = 1; $i < 7; ++$i) {
if (isset($trace[$i]['class'], $trace[$i]['function'])
&& 'dump' === $trace[$i]['function']
&& 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class']
) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
while (++$i < 7) {
if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
break;
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) {
$template = $trace[$i]['object'];
$name = $template->getTemplateName();
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
$info = $template->getDebugInfo();
if (isset($info[$trace[$i - 1]['line']])) {
$line = $info[$trace[$i - 1]['line']];
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
if ($src) {
$src = explode("\n", $src);
$fileExcerpt = array();
for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) {
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>';
}
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
}
}
break;
}
}
break;
}
}
if (false === $name) {
$name = str_replace('\\', '/', $file);
$name = substr($name, strrpos($name, '/') + 1);
}
if ($this->dumper) {
$this->doDump($data, $name, $file, $line);
}
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
++$this->dataCount;
if ($this->stopwatch) {
$this->stopwatch->stop('dump');
}
}
public function collect(Request $request, Response $response, \Exception $exception = null)
{
// Sub-requests and programmatic calls stay in the collected profile.
if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) {
return;
}
// In all other conditions that remove the web debug toolbar, dumps are written on the output.
if (!$this->requestStack
|| !$response->headers->has('X-Debug-Token')
|| $response->isRedirection()
|| ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
|| 'html' !== $request->getRequestFormat()
|| false === strripos($response->getContent(), '</body>')
) {
if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
foreach ($this->data as $dump) {
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']);
}
}
}
public function serialize()
{
if ($this->clonesCount !== $this->clonesIndex) {
return 'a:0:{}';
}
$this->data[] = $this->fileLinkFormat;
$this->data[] = $this->charset;
$ser = serialize($this->data);
$this->data = array();
$this->dataCount = 0;
$this->isCollected = true;
if (!$this->dumperIsInjected) {
$this->dumper = null;
}
return $ser;
}
public function unserialize($data)
{
parent::unserialize($data);
$charset = array_pop($this->data);
$fileLinkFormat = array_pop($this->data);
$this->dataCount = count($this->data);
self::__construct($this->stopwatch, $fileLinkFormat, $charset);
}
public function getDumpsCount()
{
return $this->dataCount;
}
public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1)
{
$data = fopen('php://memory', 'r+b');
if ('html' === $format) {
$dumper = new HtmlDumper($data, $this->charset);
} else {
throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format));
}
$dumps = array();
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));
}
$dump['data'] = stream_get_contents($data, -1, 0);
ftruncate($data, 0);
rewind($data);
$dumps[] = $dump;
}
return $dumps;
}
public function getName()
{
return 'dump';
}
public function __destruct()
{
if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) {
$this->clonesCount = 0;
$this->isCollected = true;
$h = headers_list();
$i = count($h);
array_unshift($h, 'Content-Type: '.ini_get('default_mimetype'));
while (0 !== stripos($h[$i], 'Content-Type:')) {
--$i;
}
if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
foreach ($this->data as $i => $dump) {
$this->data[$i] = null;
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']);
}
$this->data = array();
$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 instanceof HtmlDumper) {
if ($file) {
$s = $this->style('meta', '%s');
$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);
} else {
$name = sprintf('<abbr title="%s">'.$s.'</abbr>', $file, $name);
}
} else {
$name = $this->style('meta', $name);
}
$this->line = $name.' on line '.$this->style('meta', $line).':';
} else {
$this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':';
}
$this->dumpLine(0);
};
$contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper);
$contextDumper($name, $file, $line, $this->fileLinkFormat);
} else {
$cloner = new VarCloner();
$this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':'));
}
$this->dumper->dump($data);
}
private function htmlEncode($s)
{
$html = '';
$dumper = new HtmlDumper(function ($line) use (&$html) {$html .= $line;}, $this->charset);
$dumper->setDumpHeader('');
$dumper->setDumpBoundaries('', '');
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar($s));
return substr(strip_tags($html), 1, -1);
}
}

View file

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
/**
* EventDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EventDataCollector extends DataCollector implements LateDataCollectorInterface
{
protected $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher = null)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'called_listeners' => array(),
'not_called_listeners' => array(),
);
}
public function lateCollect()
{
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
$this->setCalledListeners($this->dispatcher->getCalledListeners());
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
}
}
/**
* Sets the called listeners.
*
* @param array $listeners An array of called listeners
*
* @see TraceableEventDispatcherInterface
*/
public function setCalledListeners(array $listeners)
{
$this->data['called_listeners'] = $listeners;
}
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*
* @see TraceableEventDispatcherInterface
*/
public function getCalledListeners()
{
return $this->data['called_listeners'];
}
/**
* Sets the not called listeners.
*
* @param array $listeners An array of not called listeners
*
* @see TraceableEventDispatcherInterface
*/
public function setNotCalledListeners(array $listeners)
{
$this->data['not_called_listeners'] = $listeners;
}
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*
* @see TraceableEventDispatcherInterface
*/
public function getNotCalledListeners()
{
return $this->data['not_called_listeners'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'events';
}
}

View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* ExceptionDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExceptionDataCollector extends DataCollector
{
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
if (null !== $exception) {
$this->data = array(
'exception' => FlattenException::create($exception),
);
}
}
/**
* Checks if the exception is not null.
*
* @return bool true if the exception is not null, false otherwise
*/
public function hasException()
{
return isset($this->data['exception']);
}
/**
* Gets the exception.
*
* @return \Exception The exception
*/
public function getException()
{
return $this->data['exception'];
}
/**
* Gets the exception message.
*
* @return string The exception message
*/
public function getMessage()
{
return $this->data['exception']->getMessage();
}
/**
* Gets the exception code.
*
* @return int The exception code
*/
public function getCode()
{
return $this->data['exception']->getCode();
}
/**
* Gets the status code.
*
* @return int The status code
*/
public function getStatusCode()
{
return $this->data['exception']->getStatusCode();
}
/**
* Gets the exception trace.
*
* @return array The exception trace
*/
public function getTrace()
{
return $this->data['exception']->getTrace();
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'exception';
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
/**
* LateDataCollectorInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LateDataCollectorInterface
{
/**
* Collects data as late as possible.
*/
public function lateCollect();
}

View file

@ -0,0 +1,213 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* LogDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
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;
public function __construct($logger = null)
{
if (null !== $logger && $logger instanceof DebugLoggerInterface) {
$this->logger = $logger;
}
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
// everything is done as late as possible
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
if (null !== $this->logger) {
$this->data = $this->computeErrorsCount();
$this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs());
}
}
/**
* Gets the logs.
*
* @return array An array of logs
*/
public function getLogs()
{
return isset($this->data['logs']) ? $this->data['logs'] : array();
}
public function getPriorities()
{
return isset($this->data['priorities']) ? $this->data['priorities'] : array();
}
public function countErrors()
{
return isset($this->data['error_count']) ? $this->data['error_count'] : 0;
}
public function countDeprecations()
{
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
}
public function countScreams()
{
return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'logger';
}
private function sanitizeLogs($logs)
{
$errorContextById = array();
$sanitizedLogs = array();
foreach ($logs as $log) {
$context = $this->sanitizeContext($log['context']);
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);
}
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'];
}
continue;
}
$errorContextById[$errorId] = &$context;
if ($silenced) {
$context['scream'] = true;
}
$log['context'] = &$context;
unset($context);
} else {
$log['context'] = $context;
}
$sanitizedLogs[] = $log;
}
return $sanitizedLogs;
}
private function sanitizeContext($context)
{
if (is_array($context)) {
foreach ($context as $key => $value) {
$context[$key] = $this->sanitizeContext($value);
}
return $context;
}
if (is_resource($context)) {
return sprintf('Resource(%s)', get_resource_type($context));
}
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));
}
return $context;
}
private function computeErrorsCount()
{
$count = array(
'error_count' => $this->logger->countErrors(),
'deprecation_count' => 0,
'scream_count' => 0,
'priorities' => array(),
);
foreach ($this->logger->getLogs() as $log) {
if (isset($count['priorities'][$log['priority']])) {
++$count['priorities'][$log['priority']]['count'];
} else {
$count['priorities'][$log['priority']] = array(
'count' => 1,
'name' => $log['priorityName'],
);
}
if (isset($log['context']['type'], $log['context']['level'])) {
if (E_DEPRECATED === $log['context']['type'] || E_USER_DEPRECATED === $log['context']['type']) {
++$count['deprecation_count'];
} elseif (!($log['context']['type'] & $log['context']['level'])) {
++$count['scream_count'];
}
}
}
ksort($count['priorities']);
return $count;
}
}

View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* MemoryDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface
{
public function __construct()
{
$this->data = array(
'memory' => 0,
'memory_limit' => $this->convertToBytes(ini_get('memory_limit')),
);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->updateMemoryUsage();
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
$this->updateMemoryUsage();
}
/**
* Gets the memory.
*
* @return int The memory
*/
public function getMemory()
{
return $this->data['memory'];
}
/**
* Gets the PHP memory limit.
*
* @return int The memory limit
*/
public function getMemoryLimit()
{
return $this->data['memory_limit'];
}
/**
* Updates the memory usage data.
*/
public function updateMemoryUsage()
{
$this->data['memory'] = memory_get_peak_usage(true);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'memory';
}
private function convertToBytes($memoryLimit)
{
if ('-1' === $memoryLimit) {
return -1;
}
$memoryLimit = strtolower($memoryLimit);
$max = strtolower(ltrim($memoryLimit, '+'));
if (0 === strpos($max, '0x')) {
$max = intval($max, 16);
} elseif (0 === strpos($max, '0')) {
$max = intval($max, 8);
} else {
$max = (int) $max;
}
switch (substr($memoryLimit, -1)) {
case 't': $max *= 1024;
case 'g': $max *= 1024;
case 'm': $max *= 1024;
case 'k': $max *= 1024;
}
return $max;
}
}

View file

@ -0,0 +1,311 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* RequestDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RequestDataCollector extends DataCollector implements EventSubscriberInterface
{
protected $controllers;
public function __construct()
{
$this->controllers = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
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();
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;
} else {
$attributes[$key] = $this->varToString($value);
}
}
$content = null;
try {
$content = $request->getContent();
} catch (\LogicException $e) {
// the user already got the request content as a resource
$content = false;
}
$sessionMetadata = array();
$sessionAttributes = array();
$flashes = array();
if ($request->hasSession()) {
$session = $request->getSession();
if ($session->isStarted()) {
$sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated());
$sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed());
$sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime();
$sessionAttributes = $session->all();
$flashes = $session->getFlashBag()->peekAll();
}
}
$statusCode = $response->getStatusCode();
$this->data = array(
'format' => $request->getRequestFormat(),
'content' => $content,
'content_type' => $response->headers->get('Content-Type', 'text/html'),
'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '',
'status_code' => $statusCode,
'request_query' => $request->query->all(),
'request_request' => $request->request->all(),
'request_headers' => $request->headers->all(),
'request_server' => $request->server->all(),
'request_cookies' => $request->cookies->all(),
'request_attributes' => $attributes,
'response_headers' => $responseHeaders,
'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'] = '******';
}
if (isset($this->data['request_server']['PHP_AUTH_PW'])) {
$this->data['request_server']['PHP_AUTH_PW'] = '******';
}
if (isset($this->data['request_request']['_password'])) {
$this->data['request_request']['_password'] = '******';
}
foreach ($this->data as $key => $value) {
if (!is_array($value)) {
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;
}
}
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';
}
unset($this->controllers[$request]);
}
}
public function getPathInfo()
{
return $this->data['path_info'];
}
public function getRequestRequest()
{
return new ParameterBag($this->data['request_request']);
}
public function getRequestQuery()
{
return new ParameterBag($this->data['request_query']);
}
public function getRequestHeaders()
{
return new ParameterBag($this->data['request_headers']);
}
public function getRequestServer()
{
return new ParameterBag($this->data['request_server']);
}
public function getRequestCookies()
{
return new ParameterBag($this->data['request_cookies']);
}
public function getRequestAttributes()
{
return new ParameterBag($this->data['request_attributes']);
}
public function getResponseHeaders()
{
return new ParameterBag($this->data['response_headers']);
}
public function getSessionMetadata()
{
return $this->data['session_metadata'];
}
public function getSessionAttributes()
{
return $this->data['session_attributes'];
}
public function getFlashes()
{
return $this->data['flashes'];
}
public function getContent()
{
return $this->data['content'];
}
public function getContentType()
{
return $this->data['content_type'];
}
public function getStatusText()
{
return $this->data['status_text'];
}
public function getStatusCode()
{
return $this->data['status_code'];
}
public function getFormat()
{
return $this->data['format'];
}
public function getLocale()
{
return $this->data['locale'];
}
/**
* Gets the route name.
*
* The _route request attributes is automatically set by the Router Matcher.
*
* @return string The route
*/
public function getRoute()
{
return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : '';
}
/**
* Gets the route parameters.
*
* The _route_params request attributes is automatically set by the RouterListener.
*
* @return array The parameters
*/
public function getRouteParams()
{
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array();
}
/**
* Gets the controller.
*
* @return string The controller as a string
*/
public function getController()
{
return $this->data['controller'];
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => 'onKernelController');
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'request';
}
}

View file

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* RouterDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterDataCollector extends DataCollector
{
protected $controllers;
public function __construct()
{
$this->controllers = new \SplObjectStorage();
$this->data = array(
'redirect' => false,
'url' => null,
'route' => null,
);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
if ($response instanceof RedirectResponse) {
$this->data['redirect'] = true;
$this->data['url'] = $response->getTargetUrl();
if ($this->controllers->contains($request)) {
$this->data['route'] = $this->guessRoute($request, $this->controllers[$request]);
}
}
unset($this->controllers[$request]);
}
protected function guessRoute(Request $request, $controller)
{
return 'n/a';
}
/**
* Remembers the controller associated to each request.
*
* @param FilterControllerEvent $event The filter controller event
*/
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
/**
* @return bool Whether this request will result in a redirect
*/
public function getRedirect()
{
return $this->data['redirect'];
}
/**
* @return string|null The target URL
*/
public function getTargetUrl()
{
return $this->data['url'];
}
/**
* @return string|null The target route
*/
public function getTargetRoute()
{
return $this->data['route'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'router';
}
}

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\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* TimeDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TimeDataCollector extends DataCollector implements LateDataCollectorInterface
{
protected $kernel;
protected $stopwatch;
public function __construct(KernelInterface $kernel = null, $stopwatch = null)
{
$this->kernel = $kernel;
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
if (null !== $this->kernel) {
$startTime = $this->kernel->getStartTime();
} else {
$startTime = $request->server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME'));
}
$this->data = array(
'token' => $response->headers->get('X-Debug-Token'),
'start_time' => $startTime * 1000,
'events' => array(),
);
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
if (null !== $this->stopwatch && isset($this->data['token'])) {
$this->setEvents($this->stopwatch->getSectionEvents($this->data['token']));
}
unset($this->data['token']);
}
/**
* Sets the request events.
*
* @param array $events The request events
*/
public function setEvents(array $events)
{
foreach ($events as $event) {
$event->ensureStopped();
}
$this->data['events'] = $events;
}
/**
* Gets the request events.
*
* @return array The request events
*/
public function getEvents()
{
return $this->data['events'];
}
/**
* Gets the request elapsed time.
*
* @return float The elapsed time
*/
public function getDuration()
{
if (!isset($this->data['events']['__section__'])) {
return 0;
}
$lastEvent = $this->data['events']['__section__'];
return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime();
}
/**
* Gets the initialization time.
*
* This is the time spent until the beginning of the request handling.
*
* @return float The elapsed time
*/
public function getInitTime()
{
if (!isset($this->data['events']['__section__'])) {
return 0;
}
return $this->data['events']['__section__']->getOrigin() - $this->getStartTime();
}
/**
* Gets the request time.
*
* @return int The time
*/
public function getStartTime()
{
return $this->data['start_time'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'time';
}
}

View file

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

View file

@ -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\Debug;
@trigger_error('The '.__NAMESPACE__.'\ErrorHandler class is deprecated since version 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\ErrorHandler class instead.', E_USER_DEPRECATED);
use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler;
/**
* ErrorHandler.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class ErrorHandler extends DebugErrorHandler
{
}

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\Debug;
@trigger_error('The '.__NAMESPACE__.'\ExceptionHandler class is deprecated since version 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\ExceptionHandler class instead.', E_USER_DEPRECATED);
use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler;
/**
* ExceptionHandler converts an exception to a Response object.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class ExceptionHandler extends DebugExceptionHandler
{
}

View file

@ -0,0 +1,99 @@
<?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\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\Event;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
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 version 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
}
/**
* {@inheritdoc}
*/
protected function preDispatch($eventName, Event $event)
{
switch ($eventName) {
case KernelEvents::REQUEST:
$this->stopwatch->openSection();
break;
case KernelEvents::VIEW:
case KernelEvents::RESPONSE:
// stop only if a controller has been executed
if ($this->stopwatch->isStarted('controller')) {
$this->stopwatch->stop('controller');
}
break;
case KernelEvents::TERMINATE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
// There is a very special case when using built-in AppCache class as kernel wrapper, in the case
// of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A].
// In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID
// is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception
// which must be caught.
try {
$this->stopwatch->openSection($token);
} catch (\LogicException $e) {
}
break;
}
}
/**
* {@inheritdoc}
*/
protected function postDispatch($eventName, Event $event)
{
switch ($eventName) {
case KernelEvents::CONTROLLER:
$this->stopwatch->start('controller', 'section');
break;
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
$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');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {
}
break;
}
}
}

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\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\HttpKernel\Kernel;
/**
* Sets the classes to compile in the cache for the container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddClassesToCachePass implements CompilerPassInterface
{
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,45 @@
<?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\ContainerBuilder;
/**
* This extension sub-class provides first-class integration with the
* Config/Definition Component.
*
* You can use this as base class if
*
* a) you use the Config/Definition component for configuration,
* b) your configuration class is named "Configuration", and
* c) the configuration class resides in the DependencyInjection sub-folder.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class ConfigurableExtension extends Extension
{
/**
* {@inheritdoc}
*/
final public function load(array $configs, ContainerBuilder $container)
{
$this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container);
}
/**
* Configures the passed container according to the merged configuration.
*
* @param array $mergedConfig
* @param ContainerBuilder $container
*/
abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container);
}

View file

@ -0,0 +1,87 @@
<?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\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Scope;
/**
* Adds a managed request scope.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @deprecated since version 2.7, to be removed in 3.0.
*/
class ContainerAwareHttpKernel extends HttpKernel
{
protected $container;
/**
* Constructor.
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ContainerInterface $container A ContainerInterface instance
* @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance
* @param RequestStack $requestStack A stack for master/sub requests
* @param bool $triggerDeprecation Whether or not to trigger the deprecation warning for the ContainerAwareHttpKernel
*/
public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver, RequestStack $requestStack = null, $triggerDeprecation = true)
{
parent::__construct($dispatcher, $controllerResolver, $requestStack);
if ($triggerDeprecation) {
@trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\HttpKernel class instead.', E_USER_DEPRECATED);
}
$this->container = $container;
// the request scope might have been created before (see FrameworkBundle)
if (!$container->hasScope('request')) {
$container->addScope(new Scope('request'));
}
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
$this->container->enterScope('request');
$this->container->set('request', $request, 'request');
try {
$response = parent::handle($request, $type, $catch);
} catch (\Exception $e) {
$this->container->set('request', null, 'request');
$this->container->leaveScope('request');
throw $e;
} catch (\Throwable $e) {
$this->container->set('request', null, 'request');
$this->container->leaveScope('request');
throw $e;
}
$this->container->set('request', null, 'request');
$this->container->leaveScope('request');
return $response;
}
}

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\DependencyInjection;
use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension;
/**
* Allow adding classes to the class cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Extension extends BaseExtension
{
private $classes = array();
/**
* Gets the classes to cache.
*
* @return array An array of classes
*/
public function getClassesToCompile()
{
return $this->classes;
}
/**
* Adds classes to the class cache.
*
* @param array $classes An array of classes
*/
public function addClassesToCompile(array $classes)
{
$this->classes = array_merge($this->classes, $classes);
}
}

View file

@ -0,0 +1,78 @@
<?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\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FragmentRendererPass implements CompilerPassInterface
{
private $handlerService;
private $rendererTag;
/**
* @param string $handlerService Service name of the fragment handler in the container
* @param string $rendererTag Tag name used for fragments
*/
public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer')
{
$this->handlerService = $handlerService;
$this->rendererTag = $rendererTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->handlerService)) {
return;
}
$definition = $container->getDefinition($this->handlerService);
foreach ($container->findTaggedServiceIds($this->rendererTag) 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));
}
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));
}
}
}
}
}

View file

@ -0,0 +1,78 @@
<?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\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
/**
* Lazily loads fragment renderers from the dependency injection container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LazyLoadingFragmentHandler extends FragmentHandler
{
private $container;
private $rendererIds = array();
/**
* Constructor.
*
* 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)
{
$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);
}
/**
* Adds a service as a fragment renderer.
*
* @param string $name The service name
* @param string $renderer The render service id
*/
public function addRendererService($name, $renderer)
{
$this->rendererIds[$name] = $renderer;
}
/**
* {@inheritdoc}
*/
public function render($uri, $renderer = 'inline', array $options = array())
{
if (isset($this->rendererIds[$renderer])) {
$this->addRenderer($this->container->get($this->rendererIds[$renderer]));
unset($this->rendererIds[$renderer]);
}
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 Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Ensures certain extensions are always loaded.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass
{
private $extensions;
public function __construct(array $extensions)
{
$this->extensions = $extensions;
}
public function process(ContainerBuilder $container)
{
foreach ($this->extensions as $extension) {
if (!count($container->getExtensionConfig($extension))) {
$container->loadFromExtension($extension, array());
}
}
parent::process($container);
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
@trigger_error('The '.__NAMESPACE__.'\RegisterListenersPass is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass class instead.', E_USER_DEPRECATED);
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass as BaseRegisterListenersPass;
/**
* Compiler pass to register tagged services for an event dispatcher.
*
* @deprecated since version 2.5, to be removed in 3.0. Use the Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass class instead.
*/
class RegisterListenersPass extends BaseRegisterListenersPass
{
}

View file

@ -0,0 +1,102 @@
<?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\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Allows filtering of a controller callable.
*
* You can call getController() to retrieve the current controller. With
* setController() you can set a new controller that is used in the processing
* of the request.
*
* Controllers should be callables.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FilterControllerEvent extends KernelEvent
{
/**
* The current controller.
*/
private $controller;
public function __construct(HttpKernelInterface $kernel, $controller, Request $request, $requestType)
{
parent::__construct($kernel, $request, $requestType);
$this->setController($controller);
}
/**
* Returns the current controller.
*
* @return callable
*/
public function getController()
{
return $this->controller;
}
/**
* Sets a new controller.
*
* @param callable $controller
*
* @throws \LogicException
*/
public function setController($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

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Allows to filter a Response object.
*
* You can call getResponse() to retrieve the current response. With
* setResponse() you can set a new response that will be returned to the
* browser.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FilterResponseEvent extends KernelEvent
{
/**
* The current response object.
*
* @var Response
*/
private $response;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response)
{
parent::__construct($kernel, $request, $requestType);
$this->setResponse($response);
}
/**
* Returns the current response object.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Sets a new response object.
*
* @param Response $response
*/
public function setResponse(Response $response)
{
$this->response = $response;
}
}

View file

@ -0,0 +1,21 @@
<?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;
/**
* Triggered whenever a request is fully processed.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class FinishRequestEvent extends KernelEvent
{
}

View file

@ -0,0 +1,65 @@
<?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\Response;
/**
* Allows to create a response for a request.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseEvent extends KernelEvent
{
/**
* The response object.
*
* @var Response
*/
private $response;
/**
* Returns the response object.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Sets a response and stops event propagation.
*
* @param Response $response
*/
public function setResponse(Response $response)
{
$this->response = $response;
$this->stopPropagation();
}
/**
* Returns whether a response was set.
*
* @return bool Whether a response was set
*/
public function hasResponse()
{
return null !== $this->response;
}
}

View file

@ -0,0 +1,61 @@
<?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\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Allows to create a response for the return value of a controller.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseForControllerResultEvent extends GetResponseEvent
{
/**
* The return value of the controller.
*
* @var mixed
*/
private $controllerResult;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult)
{
parent::__construct($kernel, $request, $requestType);
$this->controllerResult = $controllerResult;
}
/**
* Returns the return value of the controller.
*
* @return mixed The controller return value
*/
public function getControllerResult()
{
return $this->controllerResult;
}
/**
* Assigns the return value of the controller.
*
* @param mixed $controllerResult The controller return value
*/
public function setControllerResult($controllerResult)
{
$this->controllerResult = $controllerResult;
}
}

View file

@ -0,0 +1,67 @@
<?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\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Allows to create a response for a thrown exception.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* You can also call setException() to replace the thrown exception. This
* exception will be thrown if no response is set during processing of this
* event.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GetResponseForExceptionEvent extends GetResponseEvent
{
/**
* The exception object.
*
* @var \Exception
*/
private $exception;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e)
{
parent::__construct($kernel, $request, $requestType);
$this->setException($e);
}
/**
* Returns the thrown exception.
*
* @return \Exception The thrown exception
*/
public function getException()
{
return $this->exception;
}
/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*
* @param \Exception $exception The thrown exception
*/
public function setException(\Exception $exception)
{
$this->exception = $exception;
}
}

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\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\Event;
/**
* Base class for events thrown in the HttpKernel component.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class KernelEvent extends Event
{
/**
* The kernel in which this event was thrown.
*
* @var HttpKernelInterface
*/
private $kernel;
/**
* The request the kernel is currently processing.
*
* @var Request
*/
private $request;
/**
* The request type the kernel is currently processing. One of
* HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST.
*
* @var int
*/
private $requestType;
public function __construct(HttpKernelInterface $kernel, Request $request, $requestType)
{
$this->kernel = $kernel;
$this->request = $request;
$this->requestType = $requestType;
}
/**
* Returns the kernel in which this event was thrown.
*
* @return HttpKernelInterface
*/
public function getKernel()
{
return $this->kernel;
}
/**
* Returns the request the kernel is currently processing.
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Returns the request type the kernel is currently processing.
*
* @return int One of HttpKernelInterface::MASTER_REQUEST and
* HttpKernelInterface::SUB_REQUEST
*/
public function getRequestType()
{
return $this->requestType;
}
/**
* Checks if this is a master request.
*
* @return bool True if the request is a master request
*/
public function isMasterRequest()
{
return HttpKernelInterface::MASTER_REQUEST === $this->requestType;
}
}

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

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* Adds configured formats to each request.
*
* @author Gildas Quemener <gildas.quemener@gmail.com>
*/
class AddRequestFormatsListener implements EventSubscriberInterface
{
/**
* @var array
*/
protected $formats;
/**
* @param array $formats
*/
public function __construct(array $formats)
{
$this->formats = $formats;
}
/**
* Adds request formats.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
foreach ($this->formats as $format => $mimeTypes) {
$event->getRequest()->setFormat($format, $mimeTypes);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(KernelEvents::REQUEST => 'onKernelRequest');
}
}

View file

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
/**
* Configures errors and exceptions handlers.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DebugHandlersListener implements EventSubscriberInterface
{
private $exceptionHandler;
private $logger;
private $levels;
private $throwAt;
private $scream;
private $fileLinkFormat;
private $firstCall = true;
/**
* @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
*/
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null)
{
$this->exceptionHandler = $exceptionHandler;
$this->logger = $logger;
$this->levels = $levels;
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null));
$this->scream = (bool) $scream;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
}
/**
* Configures the error handler.
*
* @param Event|null $event The triggering event
*/
public function configure(Event $event = null)
{
if (!$this->firstCall) {
return;
}
$this->firstCall = false;
if ($this->logger || null !== $this->throwAt) {
$handler = set_error_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler instanceof ErrorHandler) {
if ($this->logger) {
$handler->setDefaultLogger($this->logger, $this->levels);
if (is_array($this->levels)) {
$scream = 0;
foreach ($this->levels as $type => $log) {
$scream |= $type;
}
} else {
$scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels;
}
if ($this->scream) {
$handler->screamAt($scream);
}
$this->logger = $this->levels = null;
}
if (null !== $this->throwAt) {
$handler->throwAt($this->throwAt, true);
}
}
}
if (!$this->exceptionHandler) {
if ($event instanceof KernelEvent) {
if (method_exists($event->getKernel(), 'terminateWithException')) {
$this->exceptionHandler = array($event->getKernel(), 'terminateWithException');
}
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
$output = $event->getOutput();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->exceptionHandler = function ($e) use ($app, $output) {
$app->renderException($e, $output);
};
}
}
if ($this->exceptionHandler) {
$handler = set_exception_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
restore_exception_handler();
if ($handler instanceof ErrorHandler) {
$h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler;
$handler->setExceptionHandler($h);
$handler = is_array($h) ? $h[0] : null;
}
if ($handler instanceof ExceptionHandler) {
$handler->setHandler($this->exceptionHandler);
if (null !== $this->fileLinkFormat) {
$handler->setFileLinkFormat($this->fileLinkFormat);
}
}
$this->exceptionHandler = null;
}
}
public static function getSubscribedEvents()
{
$events = array(KernelEvents::REQUEST => array('configure', 2048));
if (defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = array('configure', 2048);
}
return $events;
}
}

View file

@ -0,0 +1,55 @@
<?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\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\VarDumper;
/**
* Configures dump() handler.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpListener implements EventSubscriberInterface
{
private $cloner;
private $dumper;
/**
* @param ClonerInterface $cloner Cloner service
* @param DataDumperInterface $dumper Dumper service
*/
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper)
{
$this->cloner = $cloner;
$this->dumper = $dumper;
}
public function configure()
{
$cloner = $this->cloner;
$dumper = $this->dumper;
VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var));
});
}
public static function getSubscribedEvents()
{
// Register early to have a working dump() as early as possible
return array(ConsoleEvents::COMMAND => array('configure', 1024));
}
}

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\EventListener;
@trigger_error('The '.__NAMESPACE__.'\ErrorsLoggerListener class is deprecated since version 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\EventListener\DebugHandlersListener class instead.', E_USER_DEPRECATED);
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Injects the logger into the ErrorHandler, so that it can log various errors.
*
* @author Colin Frei <colin@colinfrei.com>
* @author Konstantin Myakshin <koc-dp@yandex.ru>
*
* @deprecated since version 2.6, to be removed in 3.0. Use the DebugHandlersListener class instead.
*/
class ErrorsLoggerListener implements EventSubscriberInterface
{
private $channel;
private $logger;
public function __construct($channel, LoggerInterface $logger = null)
{
$this->channel = $channel;
$this->logger = $logger;
}
public function injectLogger()
{
if (null !== $this->logger) {
ErrorHandler::setLogger($this->logger, $this->channel);
$this->logger = null;
}
}
public static function getSubscribedEvents()
{
return array(KernelEvents::REQUEST => array('injectLogger', 2048));
}
}

View file

@ -0,0 +1,25 @@
<?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;
@trigger_error('The '.__NAMESPACE__.'\EsiListener class is deprecated since version 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\EventListener\SurrogateListener class instead.', E_USER_DEPRECATED);
/**
* EsiListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for ESI.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.6, to be removed in 3.0. Use SurrogateListener instead
*/
class EsiListener extends SurrogateListener
{
}

View file

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

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\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Handles content fragments represented by special URIs.
*
* All URL paths starting with /_fragment are handled as
* content fragments by this listener.
*
* If 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>
*/
class FragmentListener implements EventSubscriberInterface
{
private $signer;
private $fragmentPath;
/**
* Constructor.
*
* @param UriSigner $signer A UriSigner instance
* @param string $fragmentPath The path that triggers this listener
*/
public function __construct(UriSigner $signer, $fragmentPath = '/_fragment')
{
$this->signer = $signer;
$this->fragmentPath = $fragmentPath;
}
/**
* Fixes request attributes when the path is '/_fragment'.
*
* @param GetResponseEvent $event A GetResponseEvent instance
*
* @throws AccessDeniedHttpException if the request does not come from a trusted IP.
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) {
return;
}
if ($request->attributes->has('_controller')) {
// Is a sub-request: no need to parse _path but it should still be removed from query parameters as below.
$request->query->remove('_path');
return;
}
if ($event->isMasterRequest()) {
$this->validateRequest($request);
}
parse_str($request->query->get('_path', ''), $attributes);
$request->attributes->add($attributes);
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes));
$request->query->remove('_path');
}
protected function validateRequest(Request $request)
{
// is the Request safe?
if (!$request->isMethodSafe(false)) {
throw new AccessDeniedHttpException();
}
// is the Request signed?
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) {
return;
}
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 version 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)),
);
}
}

View file

@ -0,0 +1,139 @@
<?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\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* 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
{
private $router;
private $defaultLocale;
private $requestStack;
/**
* Constructor.
*
* 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)
{
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 version 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();
$request->setDefaultLocale($this->defaultLocale);
$this->setLocale($request);
$this->setRouterContext($request);
}
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if (null === $this->requestStack) {
return; // removed when requestStack is required
}
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
$this->setRouterContext($parentRequest);
}
}
private function setLocale(Request $request)
{
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
}
}
private function setRouterContext(Request $request)
{
if (null !== $this->router) {
$this->router->getContext()->setParameter('_locale', $request->getLocale());
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
}
}

View file

@ -0,0 +1,177 @@
<?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\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* ProfilerListener collects data for the current request by listening to the kernel events.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ProfilerListener implements EventSubscriberInterface
{
protected $profiler;
protected $matcher;
protected $onlyException;
protected $onlyMasterRequests;
protected $exception;
protected $requests = array();
protected $profiles;
protected $requestStack;
protected $parents;
/**
* Constructor.
*
* @param Profiler $profiler A Profiler instance
* @param RequestStack $requestStack A RequestStack instance
* @param RequestMatcherInterface|null $matcher A RequestMatcher instance
* @param bool $onlyException true if the profiler only collects data when an exception occurs, false otherwise
* @param bool $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise
*/
public function __construct(Profiler $profiler, $requestStack = null, $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;
$this->onlyMasterRequests = (bool) $onlyMasterRequests;
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requestStack = $requestStack;
}
/**
* Handles the onKernelException event.
*
* @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($this->onlyMasterRequests && !$event->isMasterRequest()) {
return;
}
$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.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$master = $event->isMasterRequest();
if ($this->onlyMasterRequests && !$master) {
return;
}
if ($this->onlyException && null === $this->exception) {
return;
}
$request = $event->getRequest();
$exception = $this->exception;
$this->exception = null;
if (null !== $this->matcher && !$this->matcher->matches($request)) {
return;
}
if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) {
return;
}
$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);
}
}
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 (isset($this->profiles[$parentRequest])) {
$this->profiles[$parentRequest]->addChild($this->profiles[$request]);
}
}
}
// save profiles
foreach ($this->profiles as $request) {
$this->profiler->saveProfile($this->profiles[$request]);
}
$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),
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
}
}

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\EventListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* ResponseListener fixes the Response headers based on the Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ResponseListener implements EventSubscriberInterface
{
private $charset;
public function __construct($charset)
{
$this->charset = $charset;
}
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if (null === $response->getCharset()) {
$response->setCharset($this->charset);
}
$response->prepare($event->getRequest());
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => 'onKernelResponse',
);
}
}

View file

@ -0,0 +1,191 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* 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>
*/
class RouterListener implements EventSubscriberInterface
{
private $matcher;
private $context;
private $logger;
private $request;
private $requestStack;
/**
* Constructor.
*
* 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
*
* @throws \InvalidArgumentException
*/
public function __construct($matcher, $requestStack = null, $context = null, $logger = null)
{
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.');
}
if (null === $context && !$matcher instanceof RequestContextAwareInterface) {
throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.');
}
$this->matcher = $matcher;
$this->context = $context ?: $matcher->getContext();
$this->requestStack = $requestStack;
$this->logger = $logger;
}
/**
* 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 version 2.4 and will be made private in 3.0.', E_USER_DEPRECATED);
$this->setCurrentRequest($request);
}
private function setCurrentRequest(Request $request = null)
{
if (null !== $request && $this->request !== $request) {
$this->context->fromRequest($request);
}
$this->request = $request;
}
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if (null === $this->requestStack) {
return; // removed when requestStack is required
}
$this->setCurrentRequest($this->requestStack->getParentRequest());
}
public function onKernelRequest(GetResponseEvent $event)
{
$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);
}
if ($request->attributes->has('_controller')) {
// routing is already done
return;
}
// add attributes based on the request (routing)
try {
// matching a request is more powerful than matching a URL path + context, so try that first
if ($this->matcher instanceof RequestMatcherInterface) {
$parameters = $this->matcher->matchRequest($request);
} else {
$parameters = $this->matcher->match($request->getPathInfo());
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Matched route "%s".', isset($parameters['_route']) ? $parameters['_route'] : 'n/a'), array(
'route_parameters' => $parameters,
'request_uri' => $request->getUri(),
));
}
$request->attributes->add($parameters);
unset($parameters['_route'], $parameters['_controller']);
$request->attributes->set('_route_params', $parameters);
} catch (ResourceNotFoundException $e) {
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message, $e);
} catch (MethodNotAllowedException $e) {
$message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods()));
throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
);
}
}

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

View file

@ -0,0 +1,53 @@
<?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\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Sets the session in the request.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class SessionListener implements EventSubscriberInterface
{
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $this->getSession();
if (null === $session || $request->hasSession()) {
return;
}
$request->setSession($session);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array('onKernelRequest', 128),
);
}
/**
* 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,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* StreamedResponseListener is responsible for sending the Response
* to the client.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamedResponseListener implements EventSubscriberInterface
{
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if ($response instanceof StreamedResponse) {
$response->send();
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -1024),
);
}
}

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\EventListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SurrogateListener implements EventSubscriberInterface
{
private $surrogate;
/**
* Constructor.
*
* @param SurrogateInterface $surrogate An SurrogateInterface instance
*/
public function __construct(SurrogateInterface $surrogate = null)
{
$this->surrogate = $surrogate;
}
/**
* Filters the Response.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest() || null === $this->surrogate) {
return;
}
$this->surrogate->addSurrogateControl($event->getResponse());
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => 'onKernelResponse',
);
}
}

View file

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

View file

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

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Validates that the headers and other information indicating the
* client IP address of a request are consistent.
*
* @author Magnus Nordlander <magnus@fervo.se>
*/
class ValidateRequestListener implements EventSubscriberInterface
{
/**
* Performs the validation.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
if ($request::getTrustedProxies()) {
// This will throw an exception if the headers are inconsistent.
$request->getClientIps();
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(
array('onKernelRequest', 256),
),
);
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* AccessDeniedHttpException.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class AccessDeniedHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(403, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* BadRequestHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class BadRequestHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(400, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* ConflictHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class ConflictHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(409, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,23 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
@trigger_error('The '.__NAMESPACE__.'\FatalErrorException class is deprecated since version 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\Exception\FatalErrorException class instead.', E_USER_DEPRECATED);
/*
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*
* @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class_exists('Symfony\Component\Debug\Exception\FatalErrorException');

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
@trigger_error('The '.__NAMESPACE__.'\FlattenException class is deprecated since version 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\Exception\FlattenException class instead.', E_USER_DEPRECATED);
/*
* FlattenException wraps a PHP Exception to be able to serialize it.
*
* Basically, this class removes all objects from the trace.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class_exists('Symfony\Component\Debug\Exception\FlattenException');

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* GoneHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class GoneHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(410, $message, $previous, array(), $code);
}
}

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\Exception;
/**
* HttpException.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class HttpException extends \RuntimeException implements HttpExceptionInterface
{
private $statusCode;
private $headers;
public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function getHeaders()
{
return $this->headers;
}
}

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\Exception;
/**
* Interface for HTTP error exceptions.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
interface HttpExceptionInterface
{
/**
* Returns the status code.
*
* @return int An HTTP response status code
*/
public function getStatusCode();
/**
* Returns response headers.
*
* @return array Response headers
*/
public function getHeaders();
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* LengthRequiredHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class LengthRequiredHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(411, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* MethodNotAllowedHttpException.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class MethodNotAllowedHttpException extends HttpException
{
/**
* Constructor.
*
* @param array $allow An array of allowed methods
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('Allow' => strtoupper(implode(', ', $allow)));
parent::__construct(405, $message, $previous, $headers, $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* NotAcceptableHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class NotAcceptableHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(406, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* NotFoundHttpException.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NotFoundHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(404, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* PreconditionFailedHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class PreconditionFailedHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(412, $message, $previous, array(), $code);
}
}

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\Exception;
/**
* PreconditionRequiredHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*
* @see http://tools.ietf.org/html/rfc6585
*/
class PreconditionRequiredHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(428, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* ServiceUnavailableHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class ServiceUnavailableHttpException extends HttpException
{
/**
* Constructor.
*
* @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array();
if ($retryAfter) {
$headers = array('Retry-After' => $retryAfter);
}
parent::__construct(503, $message, $previous, $headers, $code);
}
}

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\Exception;
/**
* TooManyRequestsHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*
* @see http://tools.ietf.org/html/rfc6585
*/
class TooManyRequestsHttpException extends HttpException
{
/**
* Constructor.
*
* @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array();
if ($retryAfter) {
$headers = array('Retry-After' => $retryAfter);
}
parent::__construct(429, $message, $previous, $headers, $code);
}
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* UnauthorizedHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class UnauthorizedHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $challenge WWW-Authenticate challenge string
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0)
{
$headers = array('WWW-Authenticate' => $challenge);
parent::__construct(401, $message, $previous, $headers, $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* UnprocessableEntityHttpException.
*
* @author Steve Hutchins <hutchinsteve@gmail.com>
*/
class UnprocessableEntityHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(422, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Exception;
/**
* UnsupportedMediaTypeHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
*/
class UnsupportedMediaTypeHttpException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct(415, $message, $previous, array(), $code);
}
}

View file

@ -0,0 +1,95 @@
<?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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\UriSigner;
/**
* Implements Surrogate rendering strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer
{
private $surrogate;
private $inlineStrategy;
private $signer;
/**
* Constructor.
*
* The "fallback" strategy when surrogate is not available should always be an
* instance of InlineFragmentRenderer.
*
* @param SurrogateInterface $surrogate An Surrogate instance
* @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported
* @param UriSigner $signer
*/
public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null)
{
$this->surrogate = $surrogate;
$this->inlineStrategy = $inlineStrategy;
$this->signer = $signer;
}
/**
* {@inheritdoc}
*
* Note that if the current Request has no surrogate capability, this method
* falls back to use the inline rendering strategy.
*
* Additional available options:
*
* * alt: an alternative URI to render in case of an error
* * comment: a comment to add when returning the surrogate tag
*
* Note, that not all surrogate strategies support all options. For now
* 'alt' and 'comment' are only supported by ESI.
*
* @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface
*/
public function render($uri, Request $request, array $options = array())
{
if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) {
return $this->inlineStrategy->render($uri, $request, $options);
}
if ($uri instanceof ControllerReference) {
$uri = $this->generateSignedFragmentUri($uri, $request);
}
$alt = isset($options['alt']) ? $options['alt'] : null;
if ($alt instanceof ControllerReference) {
$alt = $this->generateSignedFragmentUri($alt, $request);
}
$tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
return new Response($tag);
}
private function generateSignedFragmentUri($uri, Request $request)
{
if (null === $this->signer) {
throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.');
}
// we need to sign the absolute URI, but want to return the path only.
$fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true));
return substr($fragmentUri, strlen($request->getSchemeAndHttpHost()));
}
}

View file

@ -0,0 +1,28 @@
<?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\Fragment;
/**
* Implements the ESI rendering strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'esi';
}
}

View file

@ -0,0 +1,168 @@
<?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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
/**
* Renders a URI that represents a resource fragment.
*
* 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
*/
class FragmentHandler
{
private $debug;
private $renderers = array();
private $request;
private $requestStack;
/**
* Constructor.
*
* 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)
{
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);
}
$this->debug = $debug;
}
/**
* Adds a renderer.
*
* @param FragmentRendererInterface $renderer A FragmentRendererInterface instance
*/
public function addRenderer(FragmentRendererInterface $renderer)
{
$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 version 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
$this->request = $request;
}
/**
* Renders a URI and returns the Response content.
*
* Available options:
*
* * ignore_errors: true to return an empty string in case of an error
*
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param string $renderer The renderer name
* @param array $options An array of options
*
* @return string|null The Response content or null when the Response is streamed
*
* @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())
{
if (!isset($options['ignore_errors'])) {
$options['ignore_errors'] = !$this->debug;
}
if (!isset($this->renderers[$renderer])) {
throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer));
}
if (!$request = $this->getRequest()) {
throw new \LogicException('Rendering a fragment can only be done when handling a Request.');
}
return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options));
}
/**
* Delivers the Response as a string.
*
* When the Response is a StreamedResponse, the content is streamed immediately
* instead of being returned.
*
* @param Response $response A Response instance
*
* @return string|null The Response content or null when the Response is streamed
*
* @throws \RuntimeException when the Response is not successful
*/
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()));
}
if (!$response instanceof StreamedResponse) {
return $response->getContent();
}
$response->sendContent();
}
private function getRequest()
{
return $this->requestStack ? $this->requestStack->getCurrentRequest() : $this->request;
}
}

View file

@ -0,0 +1,42 @@
<?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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpFoundation\Response;
/**
* Interface implemented by all rendering strategies.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FragmentRendererInterface
{
/**
* Renders a URI and returns the Response content.
*
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param Request $request A Request instance
* @param array $options An array of options
*
* @return Response A Response instance
*/
public function render($uri, Request $request, array $options = array());
/**
* Gets the name of the strategy.
*
* @return string The strategy name
*/
public function getName();
}

View file

@ -0,0 +1,168 @@
<?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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\UriSigner;
/**
* Implements the Hinclude rendering strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HIncludeFragmentRenderer extends RoutableFragmentRenderer
{
private $globalDefaultTemplate;
private $signer;
private $templating;
private $charset;
/**
* Constructor.
*
* @param EngineInterface|\Twig_Environment $templating An EngineInterface or a \Twig_Environment instance
* @param UriSigner $signer A UriSigner instance
* @param string $globalDefaultTemplate The global default content (it can be a template name or the content)
* @param string $charset
*/
public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8')
{
$this->setTemplating($templating);
$this->globalDefaultTemplate = $globalDefaultTemplate;
$this->signer = $signer;
$this->charset = $charset;
}
/**
* Sets the templating engine to use to render the default content.
*
* @param EngineInterface|\Twig_Environment|null $templating An EngineInterface or a \Twig_Environment instance
*
* @throws \InvalidArgumentException
*/
public function setTemplating($templating)
{
if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) {
throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface');
}
$this->templating = $templating;
}
/**
* Checks if a templating engine has been set.
*
* @return bool true if the templating engine has been set, false otherwise
*/
public function hasTemplating()
{
return null !== $this->templating;
}
/**
* {@inheritdoc}
*
* Additional available options:
*
* * default: The default content (it can be a template name or the content)
* * 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())
{
if ($uri instanceof ControllerReference) {
if (null === $this->signer) {
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.');
}
// we need to sign the absolute URI, but want to return the path only.
$uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), strlen($request->getSchemeAndHttpHost()));
}
// We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content.
$uri = str_replace('&', '&amp;', $uri);
$template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate;
if (null !== $this->templating && $template && $this->templateExists($template)) {
$content = $this->templating->render($template);
} else {
$content = $template;
}
$attributes = isset($options['attributes']) && is_array($options['attributes']) ? $options['attributes'] : array();
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;
}
foreach ($attributes as $attribute => $value) {
$renderedAttributes .= sprintf(
' %s="%s"',
htmlspecialchars($attribute, $flags, $this->charset, false),
htmlspecialchars($value, $flags, $this->charset, false)
);
}
}
return new Response(sprintf('<hx:include src="%s"%s>%s</hx:include>', $uri, $renderedAttributes, $content));
}
/**
* @param string $template
*
* @return bool
*/
private function templateExists($template)
{
if ($this->templating instanceof EngineInterface) {
try {
return $this->templating->exists($template);
} catch (\InvalidArgumentException $e) {
return false;
}
}
$loader = $this->templating->getLoader();
if ($loader instanceof \Twig_ExistsLoaderInterface || method_exists($loader, 'exists')) {
return $loader->exists($template);
}
try {
if (method_exists($loader, 'getSourceContext')) {
$loader->getSourceContext($template);
} else {
$loader->getSource($template);
}
return true;
} catch (\Twig_Error_Loader $e) {
}
return false;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'hinclude';
}
}

View file

@ -0,0 +1,154 @@
<?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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InlineFragmentRenderer extends RoutableFragmentRenderer
{
private $kernel;
private $dispatcher;
/**
* Constructor.
*
* @param HttpKernelInterface $kernel A HttpKernelInterface instance
* @param EventDispatcherInterface $dispatcher A EventDispatcherInterface instance
*/
public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null)
{
$this->kernel = $kernel;
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*
* Additional available options:
*
* * alt: an alternative URI to render in case of an error
*/
public function render($uri, Request $request, array $options = array())
{
$reference = null;
if ($uri instanceof ControllerReference) {
$reference = $uri;
// Remove attributes from the generated URI because if not, the Symfony
// routing system will use them to populate the Request attributes. We don't
// want that as we want to preserve objects (so we manually set Request attributes
// below instead)
$attributes = $reference->attributes;
$reference->attributes = array();
// The request format and locale might have been overridden by the user
foreach (array('_format', '_locale') as $key) {
if (isset($attributes[$key])) {
$reference->attributes[$key] = $attributes[$key];
}
}
$uri = $this->generateFragmentUri($uri, $request, false, false);
$reference->attributes = array_merge($attributes, $reference->attributes);
}
$subRequest = $this->createSubRequest($uri, $request);
// override Request attributes as they can be objects (which are not supported by the generated URI)
if (null !== $reference) {
$subRequest->attributes->add($reference->attributes);
}
$level = ob_get_level();
try {
return $this->kernel->handle($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
if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) {
$event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e);
$this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event);
}
// let's clean up the output buffers that were created by the sub-request
Response::closeOutputBuffers($level, false);
if (isset($options['alt'])) {
$alt = $options['alt'];
unset($options['alt']);
return $this->render($alt, $request, $options);
}
if (!isset($options['ignore_errors']) || !$options['ignore_errors']) {
throw $e;
}
return new Response();
}
}
protected function createSubRequest($uri, Request $request)
{
$cookies = $request->cookies->all();
$server = $request->server->all();
// Override the arguments to emulate a sub-request.
// Sub-request object will point to localhost as client ip and real client ip
// will be included into trusted header for client ip
try {
if ($trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) {
$currentXForwardedFor = $request->headers->get($trustedHeaderName, '');
$server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
}
} catch (\InvalidArgumentException $e) {
// Do nothing
}
$server['REMOTE_ADDR'] = '127.0.0.1';
unset($server['HTTP_IF_MODIFIED_SINCE']);
unset($server['HTTP_IF_NONE_MATCH']);
$subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server);
if ($request->headers->has('Surrogate-Capability')) {
$subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability'));
}
if ($session = $request->getSession()) {
$subRequest->setSession($session);
}
return $subRequest;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'inline';
}
}

View file

@ -0,0 +1,90 @@
<?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\Fragment;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\EventListener\FragmentListener;
/**
* Adds the possibility to generate a fragment URI for a given Controller.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class RoutableFragmentRenderer implements FragmentRendererInterface
{
private $fragmentPath = '/_fragment';
/**
* Sets the fragment path that triggers the fragment listener.
*
* @param string $path The path
*
* @see FragmentListener
*/
public function setFragmentPath($path)
{
$this->fragmentPath = $path;
}
/**
* Generates a fragment URI for a given controller.
*
* @param ControllerReference $reference A ControllerReference instance
* @param Request $request A Request instance
* @param bool $absolute Whether to generate an absolute URL or not
* @param bool $strict Whether to allow non-scalar attributes or not
*
* @return string A fragment URI
*/
protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true)
{
if ($strict) {
$this->checkNonScalar($reference->attributes);
}
// We need to forward the current _format and _locale values as we don't have
// a proper routing pattern to do the job for us.
// This makes things inconsistent if you switch from rendering a controller
// to rendering a route if the route pattern does not contain the special
// _format and _locale placeholders.
if (!isset($reference->attributes['_format'])) {
$reference->attributes['_format'] = $request->getRequestFormat();
}
if (!isset($reference->attributes['_locale'])) {
$reference->attributes['_locale'] = $request->getLocale();
}
$reference->attributes['_controller'] = $reference->controller;
$reference->query['_path'] = http_build_query($reference->attributes, '', '&');
$path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&');
if ($absolute) {
return $request->getUriForPath($path);
}
return $request->getBaseUrl().$path;
}
private function checkNonScalar($values)
{
foreach ($values as $key => $value) {
if (is_array($value)) {
$this->checkNonScalar($value);
} elseif (!is_scalar($value) && null !== $value) {
throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key));
}
}
}
}

View file

@ -0,0 +1,28 @@
<?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\Fragment;
/**
* Implements the SSI rendering strategy.
*
* @author Sebastian Krebs <krebs.seb@gmail.com>
*/
class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'ssi';
}
}

View file

@ -0,0 +1,295 @@
<?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;
/**
* Esi implements the ESI capabilities to Request and Response instances.
*
* For more information, read the following W3C notes:
*
* * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang)
*
* * Edge Architecture Specification (http://www.w3.org/TR/edge-arch)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Esi implements SurrogateInterface
{
private $contentTypes;
private $phpEscapeMap = array(
array('<?', '<%', '<s', '<S'),
array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'),
);
/**
* Constructor.
*
* @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.
*
* @param Request $request A Request instance
*
* @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 version 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.
*
* @param Request $request A Request instance
*/
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 version 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.
*
* @param Response $response A Response instance
*/
public function addSurrogateControl(Response $response)
{
if (false !== strpos($response->getContent(), '<esi:include')) {
$response->headers->set('Surrogate-Control', 'content="ESI/1.0"');
}
}
/**
* 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
*/
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 version 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
*/
public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '')
{
$html = sprintf('<esi:include src="%s"%s%s />',
$uri,
$ignoreErrors ? ' onerror="continue"' : '',
$alt ? sprintf(' alt="%s"', $alt) : ''
);
if (!empty($comment)) {
return sprintf("<esi:comment text=\"%s\" />\n%s", $comment, $html);
}
return $html;
}
/**
* Replaces a Response ESI tags with the included resource content.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
*
* @return Response
*/
public function process(Request $request, Response $response)
{
$type = $response->headers->get('Content-Type');
if (empty($type)) {
$type = 'text/html';
}
$parts = explode(';', $type);
if (!in_array($parts[0], $this->contentTypes)) {
return $response;
}
// we don't use a proper XML parser here as we can have ESI tags in a plain text response
$content = $response->getContent();
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
$content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
$i = 1;
while (isset($chunks[$i])) {
$options = array();
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
}
if (!isset($options['src'])) {
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
$chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>'."\n",
var_export($options['src'], true),
var_export(isset($options['alt']) ? $options['alt'] : '', true),
isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false'
);
++$i;
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
++$i;
}
$content = implode('', $chunks);
$response->setContent($content);
$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));
}
}
}
/**
* 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;
}
}
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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.
*/
namespace Symfony\Component\HttpKernel\HttpCache;
@trigger_error('The '.__NAMESPACE__.'\EsiResponseCacheStrategy class is deprecated since version 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy class instead.', E_USER_DEPRECATED);
/**
* EsiResponseCacheStrategy knows how to compute the Response cache HTTP header
* based on the different ESI response cache headers.
*
* This implementation changes the master response TTL to the smallest TTL received
* or force validation if one of the ESI has validation cache strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategy instead
*/
class EsiResponseCacheStrategy extends ResponseCacheStrategy implements EsiResponseCacheStrategyInterface
{
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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.
*/
namespace Symfony\Component\HttpKernel\HttpCache;
/**
* ResponseCacheStrategyInterface implementations know how to compute the
* Response cache HTTP header based on the different response cache headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategyInterface instead.
*/
interface EsiResponseCacheStrategyInterface extends ResponseCacheStrategyInterface
{
}

View file

@ -0,0 +1,713 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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.
*/
namespace Symfony\Component\HttpKernel\HttpCache;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Cache provides HTTP caching.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpCache implements HttpKernelInterface, TerminableInterface
{
private $kernel;
private $store;
private $request;
private $surrogate;
private $surrogateCacheStrategy;
private $options = array();
private $traces = array();
/**
* Constructor.
*
* The available options are:
*
* * debug: If true, the traces are added as a HTTP header to ease debugging
*
* * default_ttl The number of seconds that a cache entry should be considered
* fresh when no explicit freshness information is provided in
* a response. Explicit Cache-Control or Expires headers
* override this value. (default: 0)
*
* * private_headers Set of request headers that trigger "private" cache-control behavior
* on responses that don't explicitly state whether the response is
* public or private via a Cache-Control directive. (default: Authorization and Cookie)
*
* * allow_reload Specifies whether the client can force a cache reload by including a
* Cache-Control "no-cache" directive in the request. Set it to ``true``
* for compliance with RFC 2616. (default: false)
*
* * allow_revalidate Specifies whether the client can force a cache revalidate by including
* a Cache-Control "max-age=0" directive in the request. Set it to ``true``
* for compliance with RFC 2616. (default: false)
*
* * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the
* Response TTL precision is a second) during which the cache can immediately return
* a stale response while it revalidates it in the background (default: 2).
* This setting is overridden by the stale-while-revalidate HTTP Cache-Control
* extension (see RFC 5861).
*
* * stale_if_error Specifies the default number of seconds (the granularity is the second) during which
* the cache can serve a stale response when an error is encountered (default: 60).
* This setting is overridden by the stale-if-error HTTP Cache-Control extension
* (see RFC 5861).
*
* @param HttpKernelInterface $kernel An HttpKernelInterface instance
* @param StoreInterface $store A Store instance
* @param SurrogateInterface $surrogate A SurrogateInterface instance
* @param array $options An array of options
*/
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array())
{
$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'));
$this->options = array_merge(array(
'debug' => false,
'default_ttl' => 0,
'private_headers' => array('Authorization', 'Cookie'),
'allow_reload' => false,
'allow_revalidate' => false,
'stale_while_revalidate' => 2,
'stale_if_error' => 60,
), $options);
}
/**
* Gets the current store.
*
* @return StoreInterface $store A StoreInterface instance
*/
public function getStore()
{
return $this->store;
}
/**
* Returns an array of events that took place during processing of the last request.
*
* @return array An array of events
*/
public function getTraces()
{
return $this->traces;
}
/**
* Returns a log message for the events of the last request processing.
*
* @return string A log message
*/
public function getLog()
{
$log = array();
foreach ($this->traces as $request => $traces) {
$log[] = sprintf('%s: %s', $request, implode(', ', $traces));
}
return implode('; ', $log);
}
/**
* Gets the Request instance associated with the master request.
*
* @return Request A Request instance
*/
public function getRequest()
{
return $this->request;
}
/**
* Gets the Kernel instance.
*
* @return HttpKernelInterface An HttpKernelInterface instance
*/
public function getKernel()
{
return $this->kernel;
}
/**
* Gets the Surrogate instance.
*
* @return SurrogateInterface A Surrogate instance
*
* @throws \LogicException
*/
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 version 2.6 and will be removed in 3.0. Use the getSurrogate() method instead.', E_USER_DEPRECATED);
return $this->getSurrogate();
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
// 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->request = $request;
if (null !== $this->surrogate) {
$this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy();
}
}
$path = $request->getPathInfo();
if ($qs = $request->getQueryString()) {
$path .= '?'.$qs;
}
$this->traces[$request->getMethod().' '.$path] = array();
if (!$request->isMethodSafe(false)) {
$response = $this->invalidate($request, $catch);
} elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) {
$response = $this->pass($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());
}
if (null !== $this->surrogate) {
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->surrogateCacheStrategy->update($response);
} else {
$this->surrogateCacheStrategy->add($response);
}
}
$response->prepare($request);
$response->isNotModified($request);
return $response;
}
/**
* {@inheritdoc}
*/
public function terminate(Request $request, Response $response)
{
if ($this->getKernel() instanceof TerminableInterface) {
$this->getKernel()->terminate($request, $response);
}
}
/**
* Forwards the Request to the backend without storing the Response in the cache.
*
* @param Request $request A Request instance
* @param bool $catch Whether to process exceptions
*
* @return Response A Response instance
*/
protected function pass(Request $request, $catch = false)
{
$this->record($request, 'pass');
return $this->forward($request, $catch);
}
/**
* Invalidates non-safe methods (like POST, PUT, and DELETE).
*
* @param Request $request A Request instance
* @param bool $catch Whether to process exceptions
*
* @return Response A Response instance
*
* @throws \Exception
*
* @see RFC2616 13.10
*/
protected function invalidate(Request $request, $catch = false)
{
$response = $this->pass($request, $catch);
// invalidate only when the response is successful
if ($response->isSuccessful() || $response->isRedirect()) {
try {
$this->store->invalidate($request);
// As per the RFC, invalidate Location and Content-Location URLs if present
foreach (array('Location', 'Content-Location') as $header) {
if ($uri = $response->headers->get($header)) {
$subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all());
$this->store->invalidate($subRequest);
}
}
$this->record($request, 'invalidate');
} catch (\Exception $e) {
$this->record($request, 'invalidate-failed');
if ($this->options['debug']) {
throw $e;
}
}
}
return $response;
}
/**
* Lookups a Response from the cache for the given Request.
*
* When a matching cache entry is found and is fresh, it uses it as the
* response without forwarding any request to the backend. When a matching
* cache entry is found but is stale, it attempts to "validate" the entry with
* the backend using conditional GET. When no matching cache entry is found,
* it triggers "miss" processing.
*
* @param Request $request A Request instance
* @param bool $catch whether to process exceptions
*
* @return Response A Response instance
*
* @throws \Exception
*/
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) {
$this->record($request, 'lookup-failed');
if ($this->options['debug']) {
throw $e;
}
return $this->pass($request, $catch);
}
if (null === $entry) {
$this->record($request, 'miss');
return $this->fetch($request, $catch);
}
if (!$this->isFreshEnough($request, $entry)) {
$this->record($request, 'stale');
return $this->validate($request, $entry, $catch);
}
$this->record($request, 'fresh');
$entry->headers->set('Age', $entry->getAge());
return $entry;
}
/**
* Validates that a cache entry is fresh.
*
* The original request is used as a template for a conditional
* GET request with the backend.
*
* @param Request $request A Request instance
* @param Response $entry A Response instance to validate
* @param bool $catch Whether to process exceptions
*
* @return Response A Response instance
*/
protected function validate(Request $request, Response $entry, $catch = false)
{
$subRequest = clone $request;
// send no head requests because we want content
if ('HEAD' === $request->getMethod()) {
$subRequest->setMethod('GET');
}
// add our cached last-modified validator
$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();
$requestEtags = $request->getETags();
if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) {
$subRequest->headers->set('if_none_match', implode(', ', $etags));
}
$response = $this->forward($subRequest, $catch, $entry);
if (304 == $response->getStatusCode()) {
$this->record($request, 'valid');
// return the response and not the cache entry if the response is valid but not cached
$etag = $response->getEtag();
if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) {
return $response;
}
$entry = clone $entry;
$entry->headers->remove('Date');
foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) {
if ($response->headers->has($name)) {
$entry->headers->set($name, $response->headers->get($name));
}
}
$response = $entry;
} else {
$this->record($request, 'invalid');
}
if ($response->isCacheable()) {
$this->store($request, $response);
}
return $response;
}
/**
* 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.
*
* @param Request $request A Request instance
* @param bool $catch whether to process exceptions
*
* @return Response A Response instance
*/
protected function fetch(Request $request, $catch = false)
{
$subRequest = clone $request;
// send no head requests because we want content
if ('HEAD' === $request->getMethod()) {
$subRequest->setMethod('GET');
}
// avoid that the backend sends no content
$subRequest->headers->remove('if_modified_since');
$subRequest->headers->remove('if_none_match');
$response = $this->forward($subRequest, $catch);
if ($response->isCacheable()) {
$this->store($request, $response);
}
return $response;
}
/**
* 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)
*
* @return Response A Response instance
*/
protected function forward(Request $request, $catch = false, Response $entry = null)
{
if ($this->surrogate) {
$this->surrogate->addSurrogateCapability($request);
}
// modify the X-Forwarded-For header if needed
$forwardedFor = $request->headers->get('X-Forwarded-For');
if ($forwardedFor) {
$request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR'));
} else {
$request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR'));
}
// fix the client IP address by setting it to 127.0.0.1 as HttpCache
// is always called from the same process as the backend.
$request->server->set('REMOTE_ADDR', '127.0.0.1');
// make sure HttpCache is a trusted proxy
if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) {
$trustedProxies[] = '127.0.0.1';
Request::setTrustedProxies($trustedProxies);
}
// always a "master" request (as the real master request can be in cache)
$response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch);
// FIXME: we probably need to also catch exceptions if raw === true
// 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 === $age = $entry->headers->getCacheControlDirective('stale-if-error')) {
$age = $this->options['stale_if_error'];
}
if (abs($entry->getTtl()) < $age) {
$this->record($request, 'stale-if-error');
return $entry;
}
}
$this->processResponseBody($request, $response);
if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
$response->setPrivate();
} elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) {
$response->setTtl($this->options['default_ttl']);
}
return $response;
}
/**
* Checks whether the cache entry is "fresh enough" to satisfy the Request.
*
* @param Request $request A Request instance
* @param Response $entry A Response instance
*
* @return bool true if the cache entry if fresh enough, false otherwise
*/
protected function isFreshEnough(Request $request, Response $entry)
{
if (!$entry->isFresh()) {
return $this->lock($request, $entry);
}
if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) {
return $maxAge > 0 && $maxAge >= $entry->getAge();
}
return true;
}
/**
* Locks a Request during the call to the backend.
*
* @param Request $request A Request instance
* @param Response $entry A Response instance
*
* @return bool true if the cache entry can be returned even if it is staled, false otherwise
*/
protected function lock(Request $request, Response $entry)
{
// try to acquire a lock to call the backend
$lock = $this->store->lock($request);
// 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);
}
return true;
}
// we have the lock, call the backend
return false;
}
/**
* Writes the Response to the cache.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
*
* @throws \Exception
*/
protected function store(Request $request, Response $response)
{
if (!$response->headers->has('Date')) {
$response->setDate(\DateTime::createFromFormat('U', time()));
}
try {
$this->store->write($request, $response);
$this->record($request, 'store');
$response->headers->set('Age', $response->getAge());
} catch (\Exception $e) {
$this->record($request, 'store-failed');
if ($this->options['debug']) {
throw $e;
}
}
// now that the response is cached, release the lock
$this->store->unlock($request);
}
/**
* Restores the Response body.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
*/
private function restoreResponseBody(Request $request, Response $response)
{
if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) {
$response->setContent(null);
$response->headers->remove('X-Body-Eval');
$response->headers->remove('X-Body-File');
return;
}
if ($response->headers->has('X-Body-Eval')) {
ob_start();
if ($response->headers->has('X-Body-File')) {
include $response->headers->get('X-Body-File');
} else {
eval('; ?>'.$response->getContent().'<?php ;');
}
$response->setContent(ob_get_clean());
$response->headers->remove('X-Body-Eval');
if (!$response->headers->has('Transfer-Encoding')) {
$response->headers->set('Content-Length', strlen($response->getContent()));
}
} elseif ($response->headers->has('X-Body-File')) {
$response->setContent(file_get_contents($response->headers->get('X-Body-File')));
} else {
return;
}
$response->headers->remove('X-Body-File');
}
protected function processResponseBody(Request $request, Response $response)
{
if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) {
$this->surrogate->process($request, $response);
}
}
/**
* Checks if the Request includes authorization or other sensitive information
* that should cause the Response to be considered private by default.
*
* @param Request $request A Request instance
*
* @return bool true if the Request is private, false otherwise
*/
private function isPrivateRequest(Request $request)
{
foreach ($this->options['private_headers'] as $key) {
$key = strtolower(str_replace('HTTP_', '', $key));
if ('cookie' === $key) {
if (count($request->cookies->all())) {
return true;
}
} elseif ($request->headers->has($key)) {
return true;
}
}
return false;
}
/**
* Records that an event took place.
*
* @param Request $request A Request instance
* @param string $event The event name
*/
private function record(Request $request, $event)
{
$path = $request->getPathInfo();
if ($qs = $request->getQueryString()) {
$path .= '?'.$qs;
}
$this->traces[$request->getMethod().' '.$path][] = $event;
}
}

View file

@ -0,0 +1,93 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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.
*/
namespace Symfony\Component\HttpKernel\HttpCache;
use Symfony\Component\HttpFoundation\Response;
/**
* ResponseCacheStrategy knows how to compute the Response cache HTTP header
* based on the different response cache headers.
*
* This implementation changes the master response TTL to the smallest TTL received
* or force validation if one of the surrogates has validation cache strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ResponseCacheStrategy implements ResponseCacheStrategyInterface
{
private $cacheable = true;
private $embeddedResponses = 0;
private $ttls = array();
private $maxAges = array();
private $isNotCacheableResponseEmbedded = false;
/**
* {@inheritdoc}
*/
public function add(Response $response)
{
if ($response->isValidateable()) {
$this->cacheable = false;
} else {
$maxAge = $response->getMaxAge();
$this->ttls[] = $response->getTtl();
$this->maxAges[] = $maxAge;
if (null === $maxAge) {
$this->isNotCacheableResponseEmbedded = true;
}
}
++$this->embeddedResponses;
}
/**
* {@inheritdoc}
*/
public function update(Response $response)
{
// if we have no embedded Response, do nothing
if (0 === $this->embeddedResponses) {
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);
$this->cacheable = false;
}
if (!$this->cacheable) {
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
return;
}
$this->ttls[] = $response->getTtl();
$this->maxAges[] = $response->getMaxAge();
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));
}
$response->setMaxAge(0);
}
}

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