the whole shebang

This commit is contained in:
2014-11-25 16:42:40 +01:00
parent 7f74c0613e
commit ab1334c0cf
3686 changed files with 496409 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
vendor/
composer.lock
phpunit.xml
Tests/ProjectContainer.php
Tests/classes.map

View File

@@ -0,0 +1,197 @@
<?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\ContainerAware;
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>
*
* @api
*/
abstract class Bundle extends ContainerAware implements BundleInterface
{
protected $name;
protected $reflected;
protected $extension;
/**
* 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)
{
}
/**
* Returns the bundle's container extension.
*
* @return ExtensionInterface|null The container extension
*
* @throws \LogicException
*
* @api
*/
public function getContainerExtension()
{
if (null === $this->extension) {
$basename = preg_replace('/Bundle$/', '', $this->getName());
$class = $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension';
if (class_exists($class)) {
$extension = new $class();
// check naming convention
$expectedAlias = Container::underscore($basename);
if ($expectedAlias != $extension->getAlias()) {
throw new \LogicException(sprintf(
'The extension alias for the default extension of a '.
'bundle must be the underscored version of the '.
'bundle name ("%s" instead of "%s")',
$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
*
* @api
*/
public function getNamespace()
{
if (null === $this->reflected) {
$this->reflected = new \ReflectionObject($this);
}
return $this->reflected->getNamespaceName();
}
/**
* Gets the Bundle directory path.
*
* @return string The Bundle absolute path
*
* @api
*/
public function getPath()
{
if (null === $this->reflected) {
$this->reflected = new \ReflectionObject($this);
}
return dirname($this->reflected->getFileName());
}
/**
* Returns the bundle parent name.
*
* @return string The Bundle parent name it overrides or null if no parent
*
* @api
*/
public function getParent()
{
return null;
}
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*
* @api
*/
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;
}
$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 .= '\\'.strtr($relativePath, '/', '\\');
}
$r = new \ReflectionClass($ns.'\\'.$file->getBasename('.php'));
if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) {
$application->add($r->newInstance());
}
}
}
}

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\Bundle;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
* BundleInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
interface BundleInterface extends ContainerAwareInterface
{
/**
* Boots the Bundle.
*
* @api
*/
public function boot();
/**
* Shutdowns the Bundle.
*
* @api
*/
public function shutdown();
/**
* Builds the bundle.
*
* It is only ever called once when the cache is empty.
*
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @api
*/
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
*
* @api
*/
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
*
* @api
*/
public function getParent();
/**
* Returns the bundle name (the class short name).
*
* @return string The Bundle name
*
* @api
*/
public function getName();
/**
* Gets the Bundle namespace.
*
* @return string The Bundle namespace
*
* @api
*/
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
*
* @api
*/
public function getPath();
}

View File

@@ -0,0 +1,51 @@
CHANGELOG
=========
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 $clearers
*/
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,73 @@
<?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;
protected $optionalsEnabled;
public function __construct(array $warmers = array())
{
$this->setWarmers($warmers);
$this->optionalsEnabled = false;
}
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 Boolean always true
*/
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 Boolean 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,211 @@
<?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\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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\HttpKernel\TerminableInterface;
/**
* Client simulates a browser and makes requests to a Kernel object.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
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)
{
$this->kernel = $kernel;
parent::__construct($server, $history, $cookieJar);
$this->followRedirects = false;
}
/**
* {@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("'", "\\'", realpath(__DIR__.'/../../..'));
return <<<EOF
<?php
require_once '$requirePath';
\$loader = new Symfony\Component\ClassLoader\ClassLoader();
\$loader->addPrefix('Symfony', '$symfonyPath');
\$loader->register();
\$kernel = unserialize('$kernel');
echo serialize(\$kernel->handle(unserialize('$request')));
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());
$httpRequest->files->replace($this->filterFiles($httpRequest->files->all()));
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 Symfony\Component\HttpFoundation\File\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
);
}
} else {
$filtered[$key] = $value;
}
}
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,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 ('@' === $file[0]) {
return $this->kernel->locateResource($file, $this->path, $first);
}
return parent::locate($file, $currentPath, $first);
}
}

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\Controller;
/**
* 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 Symfony\Component\HttpKernel\FragmentRenderer
* @see Symfony\Component\HttpKernel\Fragment\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,164 @@
<?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>
*
* @api
*/
class ControllerResolver implements ControllerResolverInterface
{
private $logger;
/**
* Constructor.
*
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* Returns the Controller instance associated with a Request.
*
* This method looks for a '_controller' request attribute that represents
* the controller name (a string like ClassName::MethodName).
*
* @param Request $request A Request instance
*
* @return mixed|Boolean A PHP callable representing the Controller,
* or false if this resolver is not able to determine the controller
*
* @throws \InvalidArgumentException|\LogicException If the controller can't be found
*
* @api
*/
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) || (is_object($controller) && method_exists($controller, '__invoke'))) {
return $controller;
}
if (false === strpos($controller, ':')) {
if (method_exists($controller, '__invoke')) {
return new $controller;
} elseif (function_exists($controller)) {
return $controller;
}
}
$callable = $this->createController($controller);
if (!is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable.', $request->getPathInfo()));
}
return $callable;
}
/**
* Returns the arguments to pass to the controller.
*
* @param Request $request A Request instance
* @param mixed $controller A PHP callable
*
* @return array
*
* @throws \RuntimeException When value for argument given is not provided
*
* @api
*/
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());
}
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)) {
$arguments[] = $attributes[$param->name];
} elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
$arguments[] = $request;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
} 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 mixed 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(new $class(), $method);
}
}

View File

@@ -0,0 +1,63 @@
<?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>
*
* @api
*/
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 mixed|Boolean A PHP callable representing the Controller,
* or false if this resolver is not able to determine the controller
*
* @throws \InvalidArgumentException|\LogicException If the controller can't be found
*
* @api
*/
public function getController(Request $request);
/**
* Returns the arguments to pass to the controller.
*
* @param Request $request A Request instance
* @param mixed $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
*
* @api
*/
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,246 @@
<?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\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* ConfigDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfigDataCollector extends DataCollector
{
private $kernel;
private $name;
private $version;
/**
* 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,
'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_name()
);
if (isset($this->kernel)) {
foreach ($this->kernel->getBundles() as $name => $bundle) {
$this->data['bundles'][$name] = $bundle->getPath();
}
}
}
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'];
}
/**
* 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 Boolean true if debug is enabled, false otherwise
*/
public function isDebug()
{
return $this->data['debug'];
}
/**
* Returns true if the XDebug is enabled.
*
* @return Boolean true if XDebug is enabled, false otherwise
*/
public function hasXDebug()
{
return $this->data['xdebug_enabled'];
}
/**
* Returns true if EAccelerator is enabled.
*
* @return Boolean true if EAccelerator is enabled, false otherwise
*/
public function hasEAccelerator()
{
return $this->data['eaccel_enabled'];
}
/**
* Returns true if APC is enabled.
*
* @return Boolean true if APC is enabled, false otherwise
*/
public function hasApc()
{
return $this->data['apc_enabled'];
}
/**
* Returns true if Zend OPcache is enabled
*
* @return Boolean true if Zend OPcache is enabled, false otherwise
*/
public function hasZendOpcache()
{
return $this->data['zend_opcache_enabled'];
}
/**
* Returns true if XCache is enabled.
*
* @return Boolean true if XCache is enabled, false otherwise
*/
public function hasXCache()
{
return $this->data['xcache_enabled'];
}
/**
* Returns true if WinCache is enabled.
*
* @return Boolean true if WinCache is enabled, false otherwise
*/
public function hasWinCache()
{
return $this->data['wincache_enabled'];
}
/**
* Returns true if any accelerator is enabled.
*
* @return Boolean 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';
}
}

View File

@@ -0,0 +1,75 @@
<?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;
/**
* DataCollector.
*
* Children of this class must store the collected data in the data property.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
{
protected $data;
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 (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,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\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* DataCollectorInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
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
*
* @api
*/
public function collect(Request $request, Response $response, \Exception $exception = null);
/**
* Returns the name of the collector.
*
* @return string The collector name
*
* @api
*/
public function getName();
}

View File

@@ -0,0 +1,91 @@
<?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\Debug\TraceableEventDispatcherInterface;
/**
* EventDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EventDataCollector extends DataCollector
{
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'called_listeners' => array(),
'not_called_listeners' => array(),
);
}
/**
* 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\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\FlattenException;
/**
* 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 Boolean 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 integer The exception code
*/
public function getCode()
{
return $this->data['exception']->getCode();
}
/**
* Gets the status code.
*
* @return integer 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,125 @@
<?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\HttpKernel\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/**
* LogDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LoggerDataCollector extends DataCollector
{
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)
{
if (null !== $this->logger) {
$this->data = array(
'error_count' => $this->logger->countErrors(),
'logs' => $this->sanitizeLogs($this->logger->getLogs()),
'deprecation_count' => $this->computeDeprecationCount()
);
}
}
/**
* Gets the called events.
*
* @return array An array of called events
*
* @see TraceableEventDispatcherInterface
*/
public function countErrors()
{
return isset($this->data['error_count']) ? $this->data['error_count'] : 0;
}
/**
* Gets the logs.
*
* @return array An array of logs
*/
public function getLogs()
{
return isset($this->data['logs']) ? $this->data['logs'] : array();
}
public function countDeprecations()
{
return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'logger';
}
private function sanitizeLogs($logs)
{
foreach ($logs as $i => $log) {
$logs[$i]['context'] = $this->sanitizeContext($log['context']);
}
return $logs;
}
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)) {
return sprintf('Object(%s)', get_class($context));
}
return $context;
}
private function computeDeprecationCount()
{
$count = 0;
foreach ($this->logger->getLogs() as $log) {
if (isset($log['context']['type']) && ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) {
$count++;
}
}
return $count;
}
}

View File

@@ -0,0 +1,91 @@
<?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
{
public function __construct()
{
$this->data = array(
'memory' => 0,
'memory_limit' => $this->convertToBytes(strtolower(ini_get('memory_limit'))),
);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->updateMemoryUsage();
}
/**
* Gets the memory.
*
* @return integer The memory
*/
public function getMemory()
{
return $this->data['memory'];
}
/**
* Gets the PHP memory limit.
*
* @return integer 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;
}
if (preg_match('#^\+?(0x?)?(.*?)([kmg]?)$#', $memoryLimit, $match)) {
$shifts = array('' => 0, 'k' => 10, 'm' => 20, 'g' => 30);
$bases = array('' => 10, '0' => 8, '0x' => 16);
return intval($match[2], $bases[$match[1]]) << $shifts[$match[3]];
}
return 0;
}
}

View File

@@ -0,0 +1,326 @@
<?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\HeaderBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
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();
$cookies = array();
foreach ($response->headers->getCookies() as $cookie) {
$cookies[] = $this->getCookieHeader($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
}
if (count($cookies) > 0) {
$responseHeaders['Set-Cookie'] = $cookies;
}
$attributes = array();
foreach ($request->attributes->all() as $key => $value) {
if ('_route' == $key && is_object($value)) {
$attributes['_route'] = $this->varToString($value->getPath());
} elseif ('_route_params' == $key) {
foreach ($value as $key => $v) {
$attributes['_route_params'][$key] = $this->varToString($v);
}
} 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') ? $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->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 $re) {
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(),
);
} 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 HeaderBag($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 ResponseHeaderBag($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';
}
private function getCookieHeader($name, $value, $expires, $path, $domain, $secure, $httponly)
{
$cookie = sprintf('%s=%s', $name, urlencode($value));
if (0 !== $expires) {
if (is_numeric($expires)) {
$expires = (int) $expires;
} elseif ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} else {
$expires = strtotime($expires);
if (false === $expires || -1 == $expires) {
throw new \InvalidArgumentException(sprintf('The "expires" cookie parameter is not valid.', $expires));
}
}
$cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), 0, -5);
}
if ($domain) {
$cookie .= '; domain='.$domain;
}
$cookie .= '; path='.$path;
if ($secure) {
$cookie .= '; secure';
}
if ($httponly) {
$cookie .= '; httponly';
}
return $cookie;
}
}

View File

@@ -0,0 +1,103 @@
<?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\DataCollector\DataCollector;
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 Boolean 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,123 @@
<?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\DataCollector;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* TimeDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TimeDataCollector extends DataCollector
{
protected $kernel;
public function __construct(KernelInterface $kernel = null)
{
$this->kernel = $kernel;
}
/**
* {@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(
'start_time' => $startTime * 1000,
'events' => array(),
);
}
/**
* 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 integer The time
*/
public function getStartTime()
{
return $this->data['start_time'];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'time';
}
}

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\Debug;
use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler;
/**
* ErrorHandler.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Deprecated in 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,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\Debug;
use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler;
/**
* ExceptionHandler converts an exception to a Response object.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Deprecated in 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,469 @@
<?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\Stopwatch\Stopwatch;
use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEventDispatcherInterface
{
private $logger;
private $called;
private $stopwatch;
private $profiler;
private $dispatcher;
private $wrappedListeners;
private $firstCalledEvent;
private $id;
/**
* Constructor.
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
$this->wrappedListeners = array();
$this->firstCalledEvent = array();
}
/**
* Sets the profiler.
*
* @param Profiler|null $profiler A Profiler instance
*/
public function setProfiler(Profiler $profiler = null)
{
$this->profiler = $profiler;
}
/**
* {@inheritDoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
return $this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
$this->id = spl_object_hash($event);
$this->preDispatch($eventName, $event);
$e = $this->stopwatch->start($eventName, 'section');
$this->firstCalledEvent[$eventName] = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading');
if (!$this->dispatcher->hasListeners($eventName)) {
$this->firstCalledEvent[$eventName]->stop();
}
$this->dispatcher->dispatch($eventName, $event);
// reset the id as another event might have been dispatched during the dispatching of this event
$this->id = spl_object_hash($event);
unset($this->firstCalledEvent[$eventName]);
$e->stop();
$this->postDispatch($eventName, $event);
return $event;
}
/**
* {@inheritDoc}
*/
public function getCalledListeners()
{
return $this->called;
}
/**
* {@inheritDoc}
*/
public function getNotCalledListeners()
{
$notCalled = array();
foreach ($this->getListeners() as $name => $listeners) {
foreach ($listeners as $listener) {
$info = $this->getListenerInfo($listener, $name);
if (!isset($this->called[$name.'.'.$info['pretty']])) {
$notCalled[$name.'.'.$info['pretty']] = $info;
}
}
}
return $notCalled;
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call($method, $arguments)
{
return call_user_func_array(array($this->dispatcher, $method), $arguments);
}
/**
* This is a private method and must not be used.
*
* This method is public because it is used in a closure.
* Whenever Symfony will require PHP 5.4, this could be changed
* to a proper private method.
*/
public function logSkippedListeners($eventName, Event $event, $listener)
{
if (null === $this->logger) {
return;
}
$info = $this->getListenerInfo($listener, $eventName);
$this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
$skippedListeners = $this->getListeners($eventName);
$skipped = false;
foreach ($skippedListeners as $skippedListener) {
$skippedListener = $this->unwrapListener($skippedListener);
if ($skipped) {
$info = $this->getListenerInfo($skippedListener, $eventName);
$this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
}
if ($skippedListener === $listener) {
$skipped = true;
}
}
}
/**
* This is a private method.
*
* This method is public because it is used in a closure.
* Whenever Symfony will require PHP 5.4, this could be changed
* to a proper private method.
*/
public function preListenerCall($eventName, $listener)
{
// is it the first called listener?
if (isset($this->firstCalledEvent[$eventName])) {
$this->firstCalledEvent[$eventName]->stop();
unset($this->firstCalledEvent[$eventName]);
}
$info = $this->getListenerInfo($listener, $eventName);
if (null !== $this->logger) {
$this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
}
$this->called[$eventName.'.'.$info['pretty']] = $info;
return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener');
}
/**
* Returns information about the listener
*
* @param object $listener The listener
* @param string $eventName The event name
*
* @return array Informations about the listener
*/
private function getListenerInfo($listener, $eventName)
{
$listener = $this->unwrapListener($listener);
$info = array(
'event' => $eventName,
);
if ($listener instanceof \Closure) {
$info += array(
'type' => 'Closure',
'pretty' => 'closure'
);
} elseif (is_string($listener)) {
try {
$r = new \ReflectionFunction($listener);
$file = $r->getFileName();
$line = $r->getStartLine();
} catch (\ReflectionException $e) {
$file = null;
$line = null;
}
$info += array(
'type' => 'Function',
'function' => $listener,
'file' => $file,
'line' => $line,
'pretty' => $listener,
);
} elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
if (!is_array($listener)) {
$listener = array($listener, '__invoke');
}
$class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
try {
$r = new \ReflectionMethod($class, $listener[1]);
$file = $r->getFileName();
$line = $r->getStartLine();
} catch (\ReflectionException $e) {
$file = null;
$line = null;
}
$info += array(
'type' => 'Method',
'class' => $class,
'method' => $listener[1],
'file' => $file,
'line' => $line,
'pretty' => $class.'::'.$listener[1],
);
}
return $info;
}
/**
* Updates the stopwatch data in the profile hierarchy.
*
* @param string $token Profile token
* @param Boolean $updateChildren Whether to update the children altogether
*/
private function updateProfiles($token, $updateChildren)
{
if (!$this->profiler || !$profile = $this->profiler->loadProfile($token)) {
return;
}
$this->saveInfoInProfile($profile, $updateChildren);
}
/**
* Update the profiles with the timing and events information and saves them.
*
* @param Profile $profile The root profile
* @param Boolean $updateChildren Whether to update the children altogether
*/
private function saveInfoInProfile(Profile $profile, $updateChildren)
{
try {
$collector = $profile->getCollector('memory');
$collector->updateMemoryUsage();
} catch (\InvalidArgumentException $e) {
}
try {
$collector = $profile->getCollector('time');
$collector->setEvents($this->stopwatch->getSectionEvents($profile->getToken()));
} catch (\InvalidArgumentException $e) {
}
try {
$collector = $profile->getCollector('events');
$collector->setCalledListeners($this->getCalledListeners());
$collector->setNotCalledListeners($this->getNotCalledListeners());
} catch (\InvalidArgumentException $e) {
}
$this->profiler->saveProfile($profile);
if ($updateChildren) {
foreach ($profile->getChildren() as $child) {
$this->saveInfoInProfile($child, true);
}
}
}
private function preDispatch($eventName, Event $event)
{
// wrap all listeners before they are called
$this->wrappedListeners[$this->id] = new \SplObjectStorage();
$listeners = $this->dispatcher->getListeners($eventName);
foreach ($listeners as $listener) {
$this->dispatcher->removeListener($eventName, $listener);
$wrapped = $this->wrapListener($eventName, $listener);
$this->wrappedListeners[$this->id][$wrapped] = $listener;
$this->dispatcher->addListener($eventName, $wrapped);
}
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 builtin 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;
}
}
private 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);
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
// The profiles can only be updated once they have been created
// that is after the 'kernel.response' event of the main request
$this->updateProfiles($token, true);
}
break;
case KernelEvents::TERMINATE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
// 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.
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {}
// The children profiles have been updated by the previous 'kernel.response'
// event. Only the root profile need to be updated with the 'kernel.terminate'
// timing informations.
$this->updateProfiles($token, false);
break;
}
foreach ($this->wrappedListeners[$this->id] as $wrapped) {
$this->dispatcher->removeListener($eventName, $wrapped);
$this->dispatcher->addListener($eventName, $this->wrappedListeners[$this->id][$wrapped]);
}
unset($this->wrappedListeners[$this->id]);
}
private function wrapListener($eventName, $listener)
{
$self = $this;
return function (Event $event) use ($self, $eventName, $listener) {
$e = $self->preListenerCall($eventName, $listener);
call_user_func($listener, $event);
$e->stop();
if ($event->isPropagationStopped()) {
$self->logSkippedListeners($eventName, $event, $listener);
}
};
}
private function unwrapListener($listener)
{
// get the original listener
if (is_object($listener) && isset($this->wrappedListeners[$this->id][$listener])) {
return $this->wrappedListeners[$this->id][$listener];
}
return $listener;
}
}

View File

@@ -0,0 +1,47 @@
<?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\DependencyInjection\Extension;
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 you
*
* a) 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,75 @@
<?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\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>
*/
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
*/
public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver)
{
parent::__construct($dispatcher, $controllerResolver);
$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)
{
$request->headers->set('X-Php-Ob-Level', ob_get_level());
$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;
}
$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,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,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $dispatcherService;
/**
* @var string
*/
protected $listenerTag;
/**
* @var string
*/
protected $subscriberTag;
/**
* Constructor.
*
* @param string $dispatcherService Service name of the event dispatcher in processed container
* @param string $listenerTag Tag name used for listener
* @param string $subscriberTag Tag name used for subscribers
*/
public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService)) {
return;
}
$definition = $container->getDefinition($this->dispatcherService);
foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
foreach ($events as $event) {
$priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) {
throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback(array(
'/(?<=\b)[a-z]/i',
'/[^a-z0-9]/i',
), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
}
$definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
}
}
foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $container->getDefinition($id)->getClass();
$refClass = new \ReflectionClass($class);
$interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
if (!$refClass->implementsInterface($interface)) {
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$definition->addMethodCall('addSubscriberService', array($id, $class));
}
}
}

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\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>
*
* @api
*/
class FilterControllerEvent extends KernelEvent
{
/**
* The current controller
* @var callable
*/
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
*
* @api
*/
public function getController()
{
return $this->controller;
}
/**
* Sets a new controller
*
* @param callable $controller
*
* @throws \LogicException
*
* @api
*/
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,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;
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>
*
* @api
*/
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
*
* @api
*/
public function getResponse()
{
return $this->response;
}
/**
* Sets a new response object
*
* @param Response $response
*
* @api
*/
public function setResponse(Response $response)
{
$this->response = $response;
}
}

View File

@@ -0,0 +1,72 @@
<?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>
*
* @api
*/
class GetResponseEvent extends KernelEvent
{
/**
* The response object
* @var Response
*/
private $response;
/**
* Returns the response object
*
* @return Response
*
* @api
*/
public function getResponse()
{
return $this->response;
}
/**
* Sets a response and stops event propagation
*
* @param Response $response
*
* @api
*/
public function setResponse(Response $response)
{
$this->response = $response;
$this->stopPropagation();
}
/**
* Returns whether a response was set
*
* @return Boolean Whether a response was set
*
* @api
*/
public function hasResponse()
{
return null !== $this->response;
}
}

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 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>
*
* @api
*/
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
*
* @api
*/
public function getControllerResult()
{
return $this->controllerResult;
}
/**
* Assigns the return value of the controller.
*
* @param mixed The controller return value
*
* @api
*/
public function setControllerResult($controllerResult)
{
$this->controllerResult = $controllerResult;
}
}

View File

@@ -0,0 +1,72 @@
<?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>
*
* @api
*/
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
*
* @api
*/
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
*
* @api
*/
public function setException(\Exception $exception)
{
$this->exception = $exception;
}
}

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\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>
*
* @api
*/
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 integer
*/
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
*
* @api
*/
public function getKernel()
{
return $this->kernel;
}
/**
* Returns the request the kernel is currently processing
*
* @return Request
*
* @api
*/
public function getRequest()
{
return $this->request;
}
/**
* Returns the request type the kernel is currently processing
*
* @return integer One of HttpKernelInterface::MASTER_REQUEST and
* HttpKernelInterface::SUB_REQUEST
*
* @api
*/
public function getRequestType()
{
return $this->requestType;
}
}

View File

@@ -0,0 +1,72 @@
<?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\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Allows to execute logic after a response was sent
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PostResponseEvent extends Event
{
/**
* The kernel in which this event was thrown
* @var HttpKernelInterface
*/
private $kernel;
private $request;
private $response;
public function __construct(HttpKernelInterface $kernel, Request $request, Response $response)
{
$this->kernel = $kernel;
$this->request = $request;
$this->response = $response;
}
/**
* Returns the kernel in which this event was thrown.
*
* @return HttpKernelInterface
*/
public function getKernel()
{
return $this->kernel;
}
/**
* Returns the request for which this event was thrown.
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Returns the response for which this event was thrown.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\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>
*/
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);
}
}
public static function getSubscribedEvents()
{
return array(KernelEvents::REQUEST => 'injectLogger');
}
}

View File

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

View File

@@ -0,0 +1,112 @@
<?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\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Exception\FlattenException;
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)
{
static $handling;
if (true === $handling) {
return false;
}
$handling = true;
$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()));
$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 in 2.4, to be removed in 3.0
'format' => $request->getRequestFormat(),
);
$request = $request->duplicate(null, null, $attributes);
$request->setMethod('GET');
try {
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, true);
} catch (\Exception $e) {
$this->logException($exception, sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $e->getMessage()), false);
// set handling to false otherwise it wont be able to handle further more
$handling = false;
// re-throw the exception from within HttpKernel as this is a catch-all
return;
}
$event->setResponse($response);
$handling = false;
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', -128),
);
}
/**
* Logs an exception.
*
* @param \Exception $exception The original \Exception instance
* @param string $message The error message to log
* @param Boolean $original False when the handling of the exception thrown another exception
*/
protected function logException(\Exception $exception, $message, $original = true)
{
$isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
$context = array('exception' => $exception);
if (null !== $this->logger) {
if ($isCritical) {
$this->logger->critical($message, $context);
} else {
$this->logger->error($message, $context);
}
} elseif (!$original || $isCritical) {
error_log($message);
}
}
}

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\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\IpUtils;
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 the request does not come from a trusted IP, it throws an
* AccessDeniedHttpException exception.
*
* @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;
}
$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()) {
throw new AccessDeniedHttpException();
}
// does the Request come from a trusted IP?
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
$remoteAddress = $request->server->get('REMOTE_ADDR');
if (IpUtils::checkIp($remoteAddress, $trustedIps)) {
return;
}
// 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();
}
protected function getLocalIpAddresses()
{
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,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\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Initializes the locale based on the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LocaleListener implements EventSubscriberInterface
{
private $router;
private $defaultLocale;
public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null)
{
$this->defaultLocale = $defaultLocale;
$this->router = $router;
}
public function setRequest(Request $request = null)
{
if (null === $request) {
return;
}
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
}
if (null !== $this->router) {
$this->router->getContext()->setParameter('_locale', $request->getLocale());
}
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$request->setDefaultLocale($this->defaultLocale);
$this->setRequest($request);
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the Router to have access to the _locale
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
);
}
}

View File

@@ -0,0 +1,166 @@
<?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\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* ProfilerListener collects data for the current request by listening to the onKernelResponse event.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ProfilerListener implements EventSubscriberInterface
{
protected $profiler;
protected $matcher;
protected $onlyException;
protected $onlyMasterRequests;
protected $exception;
protected $children;
protected $requests;
protected $profiles;
/**
* Constructor.
*
* @param Profiler $profiler A Profiler instance
* @param RequestMatcherInterface $matcher A RequestMatcher instance
* @param Boolean $onlyException true if the profiler only collects data when an exception occurs, false otherwise
* @param Boolean $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise
*/
public function __construct(Profiler $profiler, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false)
{
$this->profiler = $profiler;
$this->matcher = $matcher;
$this->onlyException = (Boolean) $onlyException;
$this->onlyMasterRequests = (Boolean) $onlyMasterRequests;
$this->children = new \SplObjectStorage();
$this->profiles = array();
}
/**
* Handles the onKernelException event.
*
* @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($this->onlyMasterRequests && HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$this->exception = $event->getException();
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->requests[] = $event->getRequest();
}
/**
* Handles the onKernelResponse event.
*
* @param FilterResponseEvent $event A FilterResponseEvent instance
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$master = HttpKernelInterface::MASTER_REQUEST === $event->getRequestType();
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[] = $profile;
if (null !== $exception) {
foreach ($this->profiles as $profile) {
$this->profiler->saveProfile($profile);
}
return;
}
// keep the profile as the child of its parent
if (!$master) {
array_pop($this->requests);
$parent = end($this->requests);
// when simulating requests, we might not have the parent
if ($parent) {
$profiles = isset($this->children[$parent]) ? $this->children[$parent] : array();
$profiles[] = $profile;
$this->children[$parent] = $profiles;
}
}
if (isset($this->children[$request])) {
foreach ($this->children[$request] as $child) {
$profile->addChild($child);
}
$this->children[$request] = array();
}
if ($master) {
$this->saveProfiles($profile);
unset($this->children);
}
}
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',
);
}
/**
* Saves the profile hierarchy.
*
* @param Profile $profile The root profile
*/
private function saveProfiles(Profile $profile)
{
$this->profiler->saveProfile($profile);
foreach ($profile->getChildren() as $profile) {
$this->saveProfiles($profile);
}
}
}

View File

@@ -0,0 +1,59 @@
<?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\HttpKernelInterface;
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 (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
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,140 @@
<?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\KernelEvents;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
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.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterListener implements EventSubscriberInterface
{
private $matcher;
private $context;
private $logger;
private $request;
/**
* Constructor.
*
* @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher
* @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, RequestContext $context = null, LoggerInterface $logger = null)
{
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->logger = $logger;
}
/**
* Sets the current Request.
*
* The application should call this method whenever the Request
* object changes (entering a Request scope for instance, but
* also when leaving a Request scope -- especially when they are
* nested).
*
* @param Request|null $request A Request instance
*/
public function setRequest(Request $request = null)
{
if (null !== $request && $this->request !== $request) {
$this->context->fromRequest($request);
}
$this->request = $request;
}
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 setRequest even if most of the time, it has already been done to keep compatibility
// with frameworks which do not use the Symfony service container
$this->setRequest($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" (parameters: %s)', $parameters['_route'], $this->parametersToString($parameters)));
}
$request->attributes->add($parameters);
unset($parameters['_route']);
unset($parameters['_controller']);
$request->attributes->set('_route_params', $parameters);
} catch (ResourceNotFoundException $e) {
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
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(), strtoupper(implode(', ', $e->getAllowedMethods())));
throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
}
}
private function parametersToString(array $parameters)
{
$pieces = array();
foreach ($parameters as $key => $val) {
$pieces[] = sprintf('"%s": "%s"', $key, (is_string($val) ? $val : json_encode($val)));
}
return implode(', ', $pieces);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 32)),
);
}
}

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;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
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 (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
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,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 integer $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 integer $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 integer $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,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;
use Symfony\Component\Debug\Exception\FatalErrorException as DebugFatalErrorException;
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*
* @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class FatalErrorException extends DebugFatalErrorException
{
}

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\Exception;
use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException;
/**
* 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 Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
*/
class FlattenException extends DebugFlattenException
{
}

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 integer $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 integer 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 integer $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 integer $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 integer $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 integer $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 integer $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,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;
/**
* 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 integer $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 integer $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,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\Exception;
/**
* TooManyRequestsHttpException.
*
* @author Ben Ramsey <ben@benramsey.com>
* @see http://tools.ietf.org/html/rfc6585
*/
class TooManyRequestsHttpException extends HttpException
{
/**
* Constructor.
*
* @param integer|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 integer $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 integer $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;
/**
* 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 integer $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,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\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\HttpCache\Esi;
/**
* Implements the ESI rendering strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EsiFragmentRenderer extends RoutableFragmentRenderer
{
private $esi;
private $inlineStrategy;
/**
* Constructor.
*
* The "fallback" strategy when ESI is not available should always be an
* instance of InlineFragmentRenderer.
*
* @param Esi $esi An Esi instance
* @param InlineFragmentRenderer $inlineStrategy The inline strategy to use when ESI is not supported
*/
public function __construct(Esi $esi, InlineFragmentRenderer $inlineStrategy)
{
$this->esi = $esi;
$this->inlineStrategy = $inlineStrategy;
}
/**
* {@inheritdoc}
*
* Note that if the current Request has no ESI 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 an esi:include tag
*
* @see Symfony\Component\HttpKernel\HttpCache\ESI
*/
public function render($uri, Request $request, array $options = array())
{
if (!$this->esi->hasSurrogateEsiCapability($request)) {
return $this->inlineStrategy->render($uri, $request, $options);
}
if ($uri instanceof ControllerReference) {
$uri = $this->generateFragmentUri($uri, $request);
}
$alt = isset($options['alt']) ? $options['alt'] : null;
if ($alt instanceof ControllerReference) {
$alt = $this->generateFragmentUri($alt, $request);
}
$tag = $this->esi->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
return new Response($tag);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'esi';
}
}

View File

@@ -0,0 +1,127 @@
<?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\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.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see FragmentRendererInterface
*/
class FragmentHandler
{
private $debug;
private $renderers;
private $request;
/**
* Constructor.
*
* @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances
* @param Boolean $debug Whether the debug mode is enabled or not
*/
public function __construct(array $renderers = array(), $debug = false)
{
$this->renderers = array();
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.
*
* @param Request $request The current Request
*/
public function setRequest(Request $request = null)
{
$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 \RuntimeException when the Response is not successful
*/
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 (null === $this->request) {
throw new \LogicException('Rendering a fragment can only be done when handling a master Request.');
}
return $this->deliver($this->renderers[$renderer]->render($uri, $this->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->request->getUri(), $response->getStatusCode()));
}
if (!$response instanceof StreamedResponse) {
return $response->getContent();
}
$response->sendContent();
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Fragment;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
/**
* Interface implemented by all rendering strategies.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see Symfony\Component\HttpKernel\FragmentRenderer
*/
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,162 @@
<?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;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
}
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 Boolean 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.');
}
$uri = $this->signer->sign($this->generateFragmentUri($uri, $request));
}
// 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) {
foreach ($attributes as $attribute => $value) {
$renderedAttributes .= sprintf(
' %s="%s"',
htmlspecialchars($attribute, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset, false),
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset, false)
);
}
}
return new Response(sprintf('<hx:include src="%s"%s>%s</hx:include>', $uri, $renderedAttributes, $content));
}
/**
* @param string $template
*
* @return boolean
*/
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) {
return $loader->exists($template);
}
try {
$loader->getSource($template);
return true;
} catch (\Twig_Error_Loader $e) {
}
return false;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'hinclude';
}
}

View File

@@ -0,0 +1,152 @@
<?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
*/
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 overriden by the user
foreach (array('_format', '_locale') as $key) {
if (isset($attributes[$key])) {
$reference->attributes[$key] = $attributes[$key];
}
}
$uri = $this->generateFragmentUri($uri, $request);
$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
while (ob_get_level() > $level) {
ob_get_clean();
}
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';
$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,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\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 Boolean $absolute Whether to generate an absolute URL or not
*
* @return string A fragment URI
*/
protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false)
{
// 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;
}
}

View File

@@ -0,0 +1,245 @@
<?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
{
private $contentTypes;
/**
* 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;
}
/**
* Returns a new cache strategy instance.
*
* @return EsiResponseCacheStrategyInterface A EsiResponseCacheStrategyInterface instance
*/
public function createCacheStrategy()
{
return new EsiResponseCacheStrategy();
}
/**
* Checks that at least one surrogate has ESI/1.0 capability.
*
* @param Request $request A Request instance
*
* @return Boolean true if one surrogate has ESI/1.0 capability, false otherwise
*/
public function hasSurrogateEsiCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, 'ESI/1.0');
}
/**
* Adds ESI/1.0 capability to the given Request.
*
* @param Request $request A Request instance
*/
public function addSurrogateEsiCapability(Request $request)
{
$current = $request->headers->get('Surrogate-Capability');
$new = 'symfony2="ESI/1.0"';
$request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new);
}
/**
* 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 Boolean true if the Response needs to be parsed, false otherwise
*/
public function needsEsiParsing(Response $response)
{
if (!$control = $response->headers->get('Surrogate-Control')) {
return false;
}
return (Boolean) preg_match('#content="[^"]*ESI/1.0[^"]*"#', $control);
}
/**
* Renders an ESI tag.
*
* @param string $uri A URI
* @param string $alt An alternate URI
* @param Boolean $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)
{
$this->request = $request;
$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 = str_replace(array('<?', '<%'), array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>'), $content);
$content = preg_replace_callback('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', array($this, 'handleEsiIncludeTag'), $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#', '', $content);
$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 Boolean $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;
}
}
}
/**
* Handles an ESI include tag (called internally).
*
* @param array $attributes An array containing the attributes.
*
* @return string The response content for the include.
*
* @throws \RuntimeException
*/
private function handleEsiIncludeTag($attributes)
{
$options = array();
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $attributes[1], $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.');
}
return sprintf('<?php echo $this->esi->handle($this, \'%s\', \'%s\', %s) ?>'."\n",
$options['src'],
isset($options['alt']) ? $options['alt'] : null,
isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false'
);
}
}

View File

@@ -0,0 +1,85 @@
<?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;
/**
* 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>
*/
class EsiResponseCacheStrategy implements EsiResponseCacheStrategyInterface
{
private $cacheable = true;
private $embeddedResponses = 0;
private $ttls = array();
private $maxAges = array();
/**
* {@inheritdoc}
*/
public function add(Response $response)
{
if ($response->isValidateable()) {
$this->cacheable = false;
} else {
$this->ttls[] = $response->getTtl();
$this->maxAges[] = $response->getMaxAge();
}
$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 (null !== $maxAge = min($this->maxAges)) {
$response->setSharedMaxAge($maxAge);
$response->headers->set('Age', $maxAge - min($this->ttls));
}
$response->setMaxAge(0);
}
}

View File

@@ -0,0 +1,41 @@
<?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;
/**
* EsiResponseCacheStrategyInterface implementations know how to compute the
* Response cache HTTP header based on the different ESI response cache headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface EsiResponseCacheStrategyInterface
{
/**
* Adds a Response.
*
* @param Response $response
*/
public function add(Response $response);
/**
* Updates the Response HTTP headers based on the embedded Responses.
*
* @param Response $response
*/
public function update(Response $response);
}

View File

@@ -0,0 +1,688 @@
<?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;
use Symfony\Component\HttpKernel\HttpCache\Esi;
/**
* Cache provides HTTP caching.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class HttpCache implements HttpKernelInterface, TerminableInterface
{
private $kernel;
private $store;
private $request;
private $esi;
private $esiCacheStrategy;
private $traces;
/**
* 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 Esi $esi An Esi instance
* @param array $options An array of options
*/
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, Esi $esi = null, array $options = array())
{
$this->store = $store;
$this->kernel = $kernel;
// 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);
$this->esi = $esi;
$this->traces = array();
}
/**
* 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 Esi instance
*
* @return Esi An Esi instance
*/
public function getEsi()
{
return $this->esi;
}
/**
* {@inheritdoc}
*
* @api
*/
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->esi) {
$this->esiCacheStrategy = $this->esi->createCacheStrategy();
}
}
$path = $request->getPathInfo();
if ($qs = $request->getQueryString()) {
$path .= '?'.$qs;
}
$this->traces[$request->getMethod().' '.$path] = array();
if (!$request->isMethodSafe()) {
$response = $this->invalidate($request, $catch);
} elseif ($request->headers->has('expect')) {
$response = $this->pass($request, $catch);
} else {
$response = $this->lookup($request, $catch);
}
$this->restoreResponseBody($request, $response);
$response->setDate(new \DateTime(null, new \DateTimeZone('UTC')));
if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) {
$response->headers->set('X-Symfony-Cache', $this->getLog());
}
if (null !== $this->esi) {
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->esiCacheStrategy->update($response);
} else {
$this->esiCacheStrategy->add($response);
}
}
$response->prepare($request);
$response->isNotModified($request);
return $response;
}
/**
* {@inheritdoc}
*
* @api
*/
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 Boolean $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 Boolean $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, $catch);
// 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 Boolean $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);
}
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 Boolean $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
$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 Boolean $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
$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 ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
$response->setPrivate(true);
} elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) {
$response->setTtl($this->options['default_ttl']);
}
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 Boolean $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->esi) {
$this->esi->addSurrogateEsiCapability($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');
// 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);
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 Boolean 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 Boolean 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, $entry);
// 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 < 2000000) {
// 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)
{
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
*
* @return 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->esi && $this->esi->needsEsiParsing($response)) {
$this->esi->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 Boolean 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,429 @@
<?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.
*
* 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;
/**
* Store implements all the logic for storing cache metadata (Request and Response headers).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Store implements StoreInterface
{
protected $root;
private $keyCache;
private $locks;
/**
* Constructor.
*
* @param string $root The path to the cache directory
*/
public function __construct($root)
{
$this->root = $root;
if (!is_dir($this->root)) {
mkdir($this->root, 0777, true);
}
$this->keyCache = new \SplObjectStorage();
$this->locks = array();
}
/**
* Cleanups storage.
*/
public function cleanup()
{
// unlock everything
foreach ($this->locks as $lock) {
@unlink($lock);
}
$error = error_get_last();
if (1 === $error['type'] && false === headers_sent()) {
// send a 503
header('HTTP/1.0 503 Service Unavailable');
header('Retry-After: 10');
echo '503 Service Unavailable';
}
}
/**
* Locks the cache for a given Request.
*
* @param Request $request A Request instance
*
* @return Boolean|string true if the lock is acquired, the path to the current lock otherwise
*/
public function lock(Request $request)
{
$path = $this->getPath($this->getCacheKey($request).'.lck');
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
return false;
}
$lock = @fopen($path, 'x');
if (false !== $lock) {
fclose($lock);
$this->locks[] = $path;
return true;
}
return !file_exists($path) ?: $path;
}
/**
* Releases the lock for the given Request.
*
* @param Request $request A Request instance
*
* @return Boolean False if the lock file does not exist or cannot be unlocked, true otherwise
*/
public function unlock(Request $request)
{
$file = $this->getPath($this->getCacheKey($request).'.lck');
return is_file($file) ? @unlink($file) : false;
}
public function isLocked(Request $request)
{
return is_file($this->getPath($this->getCacheKey($request).'.lck'));
}
/**
* Locates a cached Response for the Request provided.
*
* @param Request $request A Request instance
*
* @return Response|null A Response instance, or null if no cache entry was found
*/
public function lookup(Request $request)
{
$key = $this->getCacheKey($request);
if (!$entries = $this->getMetadata($key)) {
return null;
}
// find a cached entry that matches the request.
$match = null;
foreach ($entries as $entry) {
if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? $entry[1]['vary'][0] : '', $request->headers->all(), $entry[0])) {
$match = $entry;
break;
}
}
if (null === $match) {
return null;
}
list($req, $headers) = $match;
if (is_file($body = $this->getPath($headers['x-content-digest'][0]))) {
return $this->restoreResponse($headers, $body);
}
// TODO the metaStore referenced an entity that doesn't exist in
// the entityStore. We definitely want to return nil but we should
// also purge the entry from the meta-store when this is detected.
return null;
}
/**
* Writes a cache entry to the store for the given Request and Response.
*
* Existing entries are read and any that match the response are removed. This
* method calls write with the new list of cache entries.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
*
* @return string The key under which the response is stored
*
* @throws \RuntimeException
*/
public function write(Request $request, Response $response)
{
$key = $this->getCacheKey($request);
$storedEnv = $this->persistRequest($request);
// write the response body to the entity store if this is the original response
if (!$response->headers->has('X-Content-Digest')) {
$digest = $this->generateContentDigest($response);
if (false === $this->save($digest, $response->getContent())) {
throw new \RuntimeException('Unable to store the entity.');
}
$response->headers->set('X-Content-Digest', $digest);
if (!$response->headers->has('Transfer-Encoding')) {
$response->headers->set('Content-Length', strlen($response->getContent()));
}
}
// read existing cache entries, remove non-varying, and add this one to the list
$entries = array();
$vary = $response->headers->get('vary');
foreach ($this->getMetadata($key) as $entry) {
if (!isset($entry[1]['vary'][0])) {
$entry[1]['vary'] = array('');
}
if ($vary != $entry[1]['vary'][0] || !$this->requestsMatch($vary, $entry[0], $storedEnv)) {
$entries[] = $entry;
}
}
$headers = $this->persistResponse($response);
unset($headers['age']);
array_unshift($entries, array($storedEnv, $headers));
if (false === $this->save($key, serialize($entries))) {
throw new \RuntimeException('Unable to store the metadata.');
}
return $key;
}
/**
* Returns content digest for $response.
*
* @param Response $response
*
* @return string
*/
protected function generateContentDigest(Response $response)
{
return 'en'.sha1($response->getContent());
}
/**
* Invalidates all cache entries that match the request.
*
* @param Request $request A Request instance
*
* @throws \RuntimeException
*/
public function invalidate(Request $request)
{
$modified = false;
$key = $this->getCacheKey($request);
$entries = array();
foreach ($this->getMetadata($key) as $entry) {
$response = $this->restoreResponse($entry[1]);
if ($response->isFresh()) {
$response->expire();
$modified = true;
$entries[] = array($entry[0], $this->persistResponse($response));
} else {
$entries[] = $entry;
}
}
if ($modified) {
if (false === $this->save($key, serialize($entries))) {
throw new \RuntimeException('Unable to store the metadata.');
}
}
}
/**
* Determines whether two Request HTTP header sets are non-varying based on
* the vary response header value provided.
*
* @param string $vary A Response vary header
* @param array $env1 A Request HTTP header array
* @param array $env2 A Request HTTP header array
*
* @return Boolean true if the two environments match, false otherwise
*/
private function requestsMatch($vary, $env1, $env2)
{
if (empty($vary)) {
return true;
}
foreach (preg_split('/[\s,]+/', $vary) as $header) {
$key = strtr(strtolower($header), '_', '-');
$v1 = isset($env1[$key]) ? $env1[$key] : null;
$v2 = isset($env2[$key]) ? $env2[$key] : null;
if ($v1 !== $v2) {
return false;
}
}
return true;
}
/**
* Gets all data associated with the given key.
*
* Use this method only if you know what you are doing.
*
* @param string $key The store key
*
* @return array An array of data associated with the key
*/
private function getMetadata($key)
{
if (false === $entries = $this->load($key)) {
return array();
}
return unserialize($entries);
}
/**
* Purges data for the given URL.
*
* @param string $url A URL
*
* @return Boolean true if the URL exists and has been purged, false otherwise
*/
public function purge($url)
{
if (is_file($path = $this->getPath($this->getCacheKey(Request::create($url))))) {
unlink($path);
return true;
}
return false;
}
/**
* Loads data for the given key.
*
* @param string $key The store key
*
* @return string The data associated with the key
*/
private function load($key)
{
$path = $this->getPath($key);
return is_file($path) ? file_get_contents($path) : false;
}
/**
* Save data for the given key.
*
* @param string $key The store key
* @param string $data The data to store
*
* @return Boolean
*/
private function save($key, $data)
{
$path = $this->getPath($key);
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
return false;
}
$tmpFile = tempnam(dirname($path), basename($path));
if (false === $fp = @fopen($tmpFile, 'wb')) {
return false;
}
@fwrite($fp, $data);
@fclose($fp);
if ($data != file_get_contents($tmpFile)) {
return false;
}
if (false === @rename($tmpFile, $path)) {
return false;
}
@chmod($path, 0666 & ~umask());
}
public function getPath($key)
{
return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6);
}
/**
* Returns a cache key for the given Request.
*
* @param Request $request A Request instance
*
* @return string A key for the given Request
*/
private function getCacheKey(Request $request)
{
if (isset($this->keyCache[$request])) {
return $this->keyCache[$request];
}
return $this->keyCache[$request] = 'md'.sha1($request->getUri());
}
/**
* Persists the Request HTTP headers.
*
* @param Request $request A Request instance
*
* @return array An array of HTTP headers
*/
private function persistRequest(Request $request)
{
return $request->headers->all();
}
/**
* Persists the Response HTTP headers.
*
* @param Response $response A Response instance
*
* @return array An array of HTTP headers
*/
private function persistResponse(Response $response)
{
$headers = $response->headers->all();
$headers['X-Status'] = array($response->getStatusCode());
return $headers;
}
/**
* Restores a Response from the HTTP headers and body.
*
* @param array $headers An array of HTTP headers for the Response
* @param string $body The Response body
*
* @return Response
*/
private function restoreResponse($headers, $body = null)
{
$status = $headers['X-Status'][0];
unset($headers['X-Status']);
if (null !== $body) {
$headers['X-Body-File'] = array($body);
}
return new Response($body, $status, $headers);
}
}

View File

@@ -0,0 +1,96 @@
<?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.
*
* 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;
/**
* Interface implemented by HTTP cache stores.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface StoreInterface
{
/**
* Locates a cached Response for the Request provided.
*
* @param Request $request A Request instance
*
* @return Response|null A Response instance, or null if no cache entry was found
*/
public function lookup(Request $request);
/**
* Writes a cache entry to the store for the given Request and Response.
*
* Existing entries are read and any that match the response are removed. This
* method calls write with the new list of cache entries.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
*
* @return string The key under which the response is stored
*/
public function write(Request $request, Response $response);
/**
* Invalidates all cache entries that match the request.
*
* @param Request $request A Request instance
*/
public function invalidate(Request $request);
/**
* Locks the cache for a given Request.
*
* @param Request $request A Request instance
*
* @return Boolean|string true if the lock is acquired, the path to the current lock otherwise
*/
public function lock(Request $request);
/**
* Releases the lock for the given Request.
*
* @param Request $request A Request instance
*
* @return Boolean False if the lock file does not exist or cannot be unlocked, true otherwise
*/
public function unlock(Request $request);
/**
* Returns whether or not a lock exists.
*
* @param Request $request A Request instance
*
* @return Boolean true if lock exists, false otherwise
*/
public function isLocked(Request $request);
/**
* Purges data for the given URL.
*
* @param string $url A URL
*
* @return Boolean true if the URL exists and has been purged, false otherwise
*/
public function purge($url);
/**
* Cleanups storage.
*/
public function cleanup();
}

View File

@@ -0,0 +1,243 @@
<?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\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* HttpKernel notifies events to convert a Request object to a Response one.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class HttpKernel implements HttpKernelInterface, TerminableInterface
{
protected $dispatcher;
protected $resolver;
/**
* Constructor
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param ControllerResolverInterface $resolver A ControllerResolverInterface instance
*
* @api
*/
public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver)
{
$this->dispatcher = $dispatcher;
$this->resolver = $resolver;
}
/**
* {@inheritdoc}
*
* @api
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
try {
return $this->handleRaw($request, $type);
} catch (\Exception $e) {
if (false === $catch) {
throw $e;
}
return $this->handleException($e, $request, $type);
}
}
/**
* {@inheritdoc}
*
* @api
*/
public function terminate(Request $request, Response $response)
{
$this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response));
}
/**
* Handles a request to convert it to a response.
*
* Exceptions are not caught.
*
* @param Request $request A Request instance
* @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
*
* @return Response A Response instance
*
* @throws \LogicException If one of the listener does not behave as expected
* @throws NotFoundHttpException When controller cannot be found
*/
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
// request
$event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
if ($event->hasResponse()) {
return $this->filterResponse($event->getResponse(), $request, $type);
}
// load controller
if (false === $controller = $this->resolver->getController($request)) {
throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo()));
}
$event = new FilterControllerEvent($this, $controller, $request, $type);
$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
$controller = $event->getController();
// controller arguments
$arguments = $this->resolver->getArguments($request, $controller);
// call controller
$response = call_user_func_array($controller, $arguments);
// view
if (!$response instanceof Response) {
$event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
if ($event->hasResponse()) {
$response = $event->getResponse();
}
if (!$response instanceof Response) {
$msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));
// the user may have forgotten to return something
if (null === $response) {
$msg .= ' Did you forget to add a return statement somewhere in your controller?';
}
throw new \LogicException($msg);
}
}
return $this->filterResponse($response, $request, $type);
}
/**
* Filters a response object.
*
* @param Response $response A Response instance
* @param Request $request An error message in case the response is not a Response object
* @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
*
* @return Response The filtered Response instance
*
* @throws \RuntimeException if the passed object is not a Response instance
*/
private function filterResponse(Response $response, Request $request, $type)
{
$event = new FilterResponseEvent($this, $request, $type, $response);
$this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);
return $event->getResponse();
}
/**
* Handles an exception by trying to convert it to a Response.
*
* @param \Exception $e An \Exception instance
* @param Request $request A Request instance
* @param integer $type The type of the request
*
* @return Response A Response instance
*
* @throws \Exception
*/
private function handleException(\Exception $e, $request, $type)
{
$event = new GetResponseForExceptionEvent($this, $request, $type, $e);
$this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event);
// a listener might have replaced the exception
$e = $event->getException();
if (!$event->hasResponse()) {
throw $e;
}
$response = $event->getResponse();
// the developer asked for a specific status code
if ($response->headers->has('X-Status-Code')) {
$response->setStatusCode($response->headers->get('X-Status-Code'));
$response->headers->remove('X-Status-Code');
} elseif (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
// ensure that we actually have an error response
if ($e instanceof HttpExceptionInterface) {
// keep the HTTP status code and headers
$response->setStatusCode($e->getStatusCode());
$response->headers->add($e->getHeaders());
} else {
$response->setStatusCode(500);
}
}
try {
return $this->filterResponse($response, $request, $type);
} catch (\Exception $e) {
return $response;
}
}
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,47 @@
<?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\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* HttpKernelInterface handles a Request to convert it to a Response.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
interface HttpKernelInterface
{
const MASTER_REQUEST = 1;
const SUB_REQUEST = 2;
/**
* Handles a Request to convert it to a Response.
*
* When $catch is true, the implementation must catch all exceptions
* and do its best to convert them to a Response instance.
*
* @param Request $request A Request instance
* @param integer $type The type of the request
* (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
* @param Boolean $catch Whether to catch exceptions or not
*
* @return Response A Response instance
*
* @throws \Exception When an Exception occurs during processing
*
* @api
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
}

View File

@@ -0,0 +1,793 @@
<?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\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
/**
* The Kernel is the heart of the Symfony system.
*
* It manages an environment made of bundles.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
abstract class Kernel implements KernelInterface, TerminableInterface
{
/**
* @var BundleInterface[]
*/
protected $bundles;
protected $bundleMap;
protected $container;
protected $rootDir;
protected $environment;
protected $debug;
protected $booted;
protected $name;
protected $startTime;
protected $loadClassCache;
const VERSION = '2.3.5-DEV';
const VERSION_ID = '20305';
const MAJOR_VERSION = '2';
const MINOR_VERSION = '3';
const RELEASE_VERSION = '5';
const EXTRA_VERSION = 'DEV';
/**
* Constructor.
*
* @param string $environment The environment
* @param Boolean $debug Whether to enable debugging or not
*
* @api
*/
public function __construct($environment, $debug)
{
$this->environment = $environment;
$this->debug = (Boolean) $debug;
$this->booted = false;
$this->rootDir = $this->getRootDir();
$this->name = $this->getName();
$this->bundles = array();
if ($this->debug) {
$this->startTime = microtime(true);
}
$this->init();
}
/**
* @deprecated Deprecated since version 2.3, to be removed in 3.0. Move your logic in the constructor instead.
*/
public function init()
{
}
public function __clone()
{
if ($this->debug) {
$this->startTime = microtime(true);
}
$this->booted = false;
$this->container = null;
}
/**
* Boots the current kernel.
*
* @api
*/
public function boot()
{
if (true === $this->booted) {
return;
}
if ($this->loadClassCache) {
$this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]);
}
// init bundles
$this->initializeBundles();
// init container
$this->initializeContainer();
foreach ($this->getBundles() as $bundle) {
$bundle->setContainer($this->container);
$bundle->boot();
}
$this->booted = true;
}
/**
* {@inheritdoc}
*
* @api
*/
public function terminate(Request $request, Response $response)
{
if (false === $this->booted) {
return;
}
if ($this->getHttpKernel() instanceof TerminableInterface) {
$this->getHttpKernel()->terminate($request, $response);
}
}
/**
* {@inheritdoc}
*
* @api
*/
public function shutdown()
{
if (false === $this->booted) {
return;
}
$this->booted = false;
foreach ($this->getBundles() as $bundle) {
$bundle->shutdown();
$bundle->setContainer(null);
}
$this->container = null;
}
/**
* {@inheritdoc}
*
* @api
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
if (false === $this->booted) {
$this->boot();
}
return $this->getHttpKernel()->handle($request, $type, $catch);
}
/**
* Gets a http kernel from the container
*
* @return HttpKernel
*/
protected function getHttpKernel()
{
return $this->container->get('http_kernel');
}
/**
* {@inheritdoc}
*
* @api
*/
public function getBundles()
{
return $this->bundles;
}
/**
* {@inheritdoc}
*
* @api
*/
public function isClassInActiveBundle($class)
{
foreach ($this->getBundles() as $bundle) {
if (0 === strpos($class, $bundle->getNamespace())) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getBundle($name, $first = true)
{
if (!isset($this->bundleMap[$name])) {
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, get_class($this)));
}
if (true === $first) {
return $this->bundleMap[$name][0];
}
return $this->bundleMap[$name];
}
/**
* Returns the file path for a given resource.
*
* A Resource can be a file or a directory.
*
* The resource name must follow the following pattern:
*
* @<BundleName>/path/to/a/file.something
*
* where BundleName is the name of the bundle
* and the remaining part is the relative path in the bundle.
*
* If $dir is passed, and the first segment of the path is "Resources",
* this method will look for a file named:
*
* $dir/<BundleName>/path/without/Resources
*
* before looking in the bundle resource folder.
*
* @param string $name A resource name to locate
* @param string $dir A directory where to look for the resource first
* @param Boolean $first Whether to return the first path or paths for all matching bundles
*
* @return string|array The absolute path of the resource or an array if $first is false
*
* @throws \InvalidArgumentException if the file cannot be found or the name is not valid
* @throws \RuntimeException if the name contains invalid/unsafe
* @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle
*
* @api
*/
public function locateResource($name, $dir = null, $first = true)
{
if ('@' !== $name[0]) {
throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name));
}
if (false !== strpos($name, '..')) {
throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name));
}
$bundleName = substr($name, 1);
$path = '';
if (false !== strpos($bundleName, '/')) {
list($bundleName, $path) = explode('/', $bundleName, 2);
}
$isResource = 0 === strpos($path, 'Resources') && null !== $dir;
$overridePath = substr($path, 9);
$resourceBundle = null;
$bundles = $this->getBundle($bundleName, false);
$files = array();
foreach ($bundles as $bundle) {
if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) {
if (null !== $resourceBundle) {
throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.',
$file,
$resourceBundle,
$dir.'/'.$bundles[0]->getName().$overridePath
));
}
if ($first) {
return $file;
}
$files[] = $file;
}
if (file_exists($file = $bundle->getPath().'/'.$path)) {
if ($first && !$isResource) {
return $file;
}
$files[] = $file;
$resourceBundle = $bundle->getName();
}
}
if (count($files) > 0) {
return $first && $isResource ? $files[0] : $files;
}
throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name));
}
/**
* {@inheritdoc}
*
* @api
*/
public function getName()
{
if (null === $this->name) {
$this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir));
}
return $this->name;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getEnvironment()
{
return $this->environment;
}
/**
* {@inheritdoc}
*
* @api
*/
public function isDebug()
{
return $this->debug;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getRootDir()
{
if (null === $this->rootDir) {
$r = new \ReflectionObject($this);
$this->rootDir = str_replace('\\', '/', dirname($r->getFileName()));
}
return $this->rootDir;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getContainer()
{
return $this->container;
}
/**
* Loads the PHP class cache.
*
* This methods only registers the fact that you want to load the cache classes.
* The cache will actually only be loaded when the Kernel is booted.
*
* That optimization is mainly useful when using the HttpCache class in which
* case the class cache is not loaded if the Response is in the cache.
*
* @param string $name The cache name prefix
* @param string $extension File extension of the resulting file
*/
public function loadClassCache($name = 'classes', $extension = '.php')
{
$this->loadClassCache = array($name, $extension);
}
/**
* Used internally.
*/
public function setClassCache(array $classes)
{
file_put_contents($this->getCacheDir().'/classes.map', sprintf('<?php return %s;', var_export($classes, true)));
}
/**
* {@inheritdoc}
*
* @api
*/
public function getStartTime()
{
return $this->debug ? $this->startTime : -INF;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getCacheDir()
{
return $this->rootDir.'/cache/'.$this->environment;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getLogDir()
{
return $this->rootDir.'/logs';
}
/**
* {@inheritdoc}
*
* @api
*/
public function getCharset()
{
return 'UTF-8';
}
protected function doLoadClassCache($name, $extension)
{
if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) {
ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension);
}
}
/**
* Initializes the data structures related to the bundle management.
*
* - the bundles property maps a bundle name to the bundle instance,
* - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first).
*
* @throws \LogicException if two bundles share a common name
* @throws \LogicException if a bundle tries to extend a non-registered bundle
* @throws \LogicException if a bundle tries to extend itself
* @throws \LogicException if two bundles extend the same ancestor
*/
protected function initializeBundles()
{
// init bundles
$this->bundles = array();
$topMostBundles = array();
$directChildren = array();
foreach ($this->registerBundles() as $bundle) {
$name = $bundle->getName();
if (isset($this->bundles[$name])) {
throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name));
}
$this->bundles[$name] = $bundle;
if ($parentName = $bundle->getParent()) {
if (isset($directChildren[$parentName])) {
throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName]));
}
if ($parentName == $name) {
throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name));
}
$directChildren[$parentName] = $name;
} else {
$topMostBundles[$name] = $bundle;
}
}
// look for orphans
if (count($diff = array_values(array_diff(array_keys($directChildren), array_keys($this->bundles))))) {
throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0]));
}
// inheritance
$this->bundleMap = array();
foreach ($topMostBundles as $name => $bundle) {
$bundleMap = array($bundle);
$hierarchy = array($name);
while (isset($directChildren[$name])) {
$name = $directChildren[$name];
array_unshift($bundleMap, $this->bundles[$name]);
$hierarchy[] = $name;
}
foreach ($hierarchy as $bundle) {
$this->bundleMap[$bundle] = $bundleMap;
array_pop($bundleMap);
}
}
}
/**
* Gets the container class.
*
* @return string The container class
*/
protected function getContainerClass()
{
return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer';
}
/**
* Gets the container's base class.
*
* All names except Container must be fully qualified.
*
* @return string
*/
protected function getContainerBaseClass()
{
return 'Container';
}
/**
* Initializes the service container.
*
* The cached version of the service container is used when fresh, otherwise the
* container is built.
*/
protected function initializeContainer()
{
$class = $this->getContainerClass();
$cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug);
$fresh = true;
if (!$cache->isFresh()) {
$container = $this->buildContainer();
$container->compile();
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
$fresh = false;
}
require_once $cache;
$this->container = new $class();
$this->container->set('kernel', $this);
if (!$fresh && $this->container->has('cache_warmer')) {
$this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'));
}
}
/**
* Returns the kernel parameters.
*
* @return array An array of kernel parameters
*/
protected function getKernelParameters()
{
$bundles = array();
foreach ($this->bundles as $name => $bundle) {
$bundles[$name] = get_class($bundle);
}
return array_merge(
array(
'kernel.root_dir' => $this->rootDir,
'kernel.environment' => $this->environment,
'kernel.debug' => $this->debug,
'kernel.name' => $this->name,
'kernel.cache_dir' => $this->getCacheDir(),
'kernel.logs_dir' => $this->getLogDir(),
'kernel.bundles' => $bundles,
'kernel.charset' => $this->getCharset(),
'kernel.container_class' => $this->getContainerClass(),
),
$this->getEnvParameters()
);
}
/**
* Gets the environment parameters.
*
* Only the parameters starting with "SYMFONY__" are considered.
*
* @return array An array of parameters
*/
protected function getEnvParameters()
{
$parameters = array();
foreach ($_SERVER as $key => $value) {
if (0 === strpos($key, 'SYMFONY__')) {
$parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
}
}
return $parameters;
}
/**
* Builds the service container.
*
* @return ContainerBuilder The compiled service container
*
* @throws \RuntimeException
*/
protected function buildContainer()
{
foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) {
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true)) {
throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir));
}
} elseif (!is_writable($dir)) {
throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir));
}
}
$container = $this->getContainerBuilder();
$container->addObjectResource($this);
$this->prepareContainer($container);
if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) {
$container->merge($cont);
}
$container->addCompilerPass(new AddClassesToCachePass($this));
return $container;
}
/**
* Prepares the ContainerBuilder before it is compiled.
*
* @param ContainerBuilder $container A ContainerBuilder instance
*/
protected function prepareContainer(ContainerBuilder $container)
{
$extensions = array();
foreach ($this->bundles as $bundle) {
if ($extension = $bundle->getContainerExtension()) {
$container->registerExtension($extension);
$extensions[] = $extension->getAlias();
}
if ($this->debug) {
$container->addObjectResource($bundle);
}
}
foreach ($this->bundles as $bundle) {
$bundle->build($container);
}
// ensure these extensions are implicitly loaded
$container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions));
}
/**
* Gets a new ContainerBuilder instance used to build the service container.
*
* @return ContainerBuilder
*/
protected function getContainerBuilder()
{
$container = new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
if (class_exists('ProxyManager\Configuration')) {
$container->setProxyInstantiator(new RuntimeInstantiator());
}
return $container;
}
/**
* Dumps the service container to PHP code in the cache.
*
* @param ConfigCache $cache The config cache
* @param ContainerBuilder $container The service container
* @param string $class The name of the class to generate
* @param string $baseClass The name of the container's base class
*/
protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass)
{
// cache the container
$dumper = new PhpDumper($container);
if (class_exists('ProxyManager\Configuration')) {
$dumper->setProxyDumper(new ProxyDumper());
}
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass));
if (!$this->debug) {
$content = self::stripComments($content);
}
$cache->write($content, $container->getResources());
}
/**
* Returns a loader for the container.
*
* @param ContainerInterface $container The service container
*
* @return DelegatingLoader The loader
*/
protected function getContainerLoader(ContainerInterface $container)
{
$locator = new FileLocator($this);
$resolver = new LoaderResolver(array(
new XmlFileLoader($container, $locator),
new YamlFileLoader($container, $locator),
new IniFileLoader($container, $locator),
new PhpFileLoader($container, $locator),
new ClosureLoader($container),
));
return new DelegatingLoader($resolver);
}
/**
* Removes comments from a PHP source string.
*
* We don't use the PHP php_strip_whitespace() function
* as we want the content to be readable and well-formatted.
*
* @param string $source A PHP string
*
* @return string The PHP string with the comments removed
*/
public static function stripComments($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$rawChunk = '';
$output = '';
$tokens = token_get_all($source);
for (reset($tokens); false !== $token = current($tokens); next($tokens)) {
if (is_string($token)) {
$rawChunk .= $token;
} elseif (T_START_HEREDOC === $token[0]) {
$output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk).$token[1];
do {
$token = next($tokens);
$output .= $token[1];
} while ($token[0] !== T_END_HEREDOC);
$rawChunk = '';
} elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
$rawChunk .= $token[1];
}
}
// replace multiple new lines with a single newline
$output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk);
return $output;
}
public function serialize()
{
return serialize(array($this->environment, $this->debug));
}
public function unserialize($data)
{
list($environment, $debug) = unserialize($data);
$this->__construct($environment, $debug);
}
}

View File

@@ -0,0 +1,105 @@
<?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;
/**
* Contains all events thrown in the HttpKernel component
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
final class KernelEvents
{
/**
* The REQUEST event occurs at the very beginning of request
* dispatching
*
* This event allows you to create a response for a request before any
* other code in the framework is executed. The event listener method
* receives a Symfony\Component\HttpKernel\Event\GetResponseEvent
* instance.
*
* @var string
*
* @api
*/
const REQUEST = 'kernel.request';
/**
* The EXCEPTION event occurs when an uncaught exception appears
*
* This event allows you to create a response for a thrown exception or
* to modify the thrown exception. The event listener method receives
* a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent
* instance.
*
* @var string
*
* @api
*/
const EXCEPTION = 'kernel.exception';
/**
* The VIEW event occurs when the return value of a controller
* is not a Response instance
*
* This event allows you to create a response for the return value of the
* controller. The event listener method receives a
* Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent
* instance.
*
* @var string
*
* @api
*/
const VIEW = 'kernel.view';
/**
* The CONTROLLER event occurs once a controller was found for
* handling a request
*
* This event allows you to change the controller that will handle the
* request. The event listener method receives a
* Symfony\Component\HttpKernel\Event\FilterControllerEvent instance.
*
* @var string
*
* @api
*/
const CONTROLLER = 'kernel.controller';
/**
* The RESPONSE event occurs once a response was created for
* replying to a request
*
* This event allows you to modify or replace the response that will be
* replied. The event listener method receives a
* Symfony\Component\HttpKernel\Event\FilterResponseEvent instance.
*
* @var string
*
* @api
*/
const RESPONSE = 'kernel.response';
/**
* The TERMINATE event occurs once a response was sent
*
* This event allows you to run expensive post-response jobs.
* The event listener method receives a
* Symfony\Component\HttpKernel\Event\PostResponseEvent instance.
*
* @var string
*/
const TERMINATE = 'kernel.terminate';
}

View File

@@ -0,0 +1,208 @@
<?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\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\Config\Loader\LoaderInterface;
/**
* The Kernel is the heart of the Symfony system.
*
* It manages an environment made of bundles.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
interface KernelInterface extends HttpKernelInterface, \Serializable
{
/**
* Returns an array of bundles to registers.
*
* @return BundleInterface[] An array of bundle instances.
*
* @api
*/
public function registerBundles();
/**
* Loads the container configuration
*
* @param LoaderInterface $loader A LoaderInterface instance
*
* @api
*/
public function registerContainerConfiguration(LoaderInterface $loader);
/**
* Boots the current kernel.
*
* @api
*/
public function boot();
/**
* Shutdowns the kernel.
*
* This method is mainly useful when doing functional testing.
*
* @api
*/
public function shutdown();
/**
* Gets the registered bundle instances.
*
* @return BundleInterface[] An array of registered bundle instances
*
* @api
*/
public function getBundles();
/**
* Checks if a given class name belongs to an active bundle.
*
* @param string $class A class name
*
* @return Boolean true if the class belongs to an active bundle, false otherwise
*
* @api
*/
public function isClassInActiveBundle($class);
/**
* Returns a bundle and optionally its descendants by its name.
*
* @param string $name Bundle name
* @param Boolean $first Whether to return the first bundle only or together with its descendants
*
* @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false
*
* @throws \InvalidArgumentException when the bundle is not enabled
*
* @api
*/
public function getBundle($name, $first = true);
/**
* Returns the file path for a given resource.
*
* A Resource can be a file or a directory.
*
* The resource name must follow the following pattern:
*
* @BundleName/path/to/a/file.something
*
* where BundleName is the name of the bundle
* and the remaining part is the relative path in the bundle.
*
* If $dir is passed, and the first segment of the path is Resources,
* this method will look for a file named:
*
* $dir/BundleName/path/without/Resources
*
* @param string $name A resource name to locate
* @param string $dir A directory where to look for the resource first
* @param Boolean $first Whether to return the first path or paths for all matching bundles
*
* @return string|array The absolute path of the resource or an array if $first is false
*
* @throws \InvalidArgumentException if the file cannot be found or the name is not valid
* @throws \RuntimeException if the name contains invalid/unsafe characters
*
* @api
*/
public function locateResource($name, $dir = null, $first = true);
/**
* Gets the name of the kernel
*
* @return string The kernel name
*
* @api
*/
public function getName();
/**
* Gets the environment.
*
* @return string The current environment
*
* @api
*/
public function getEnvironment();
/**
* Checks if debug mode is enabled.
*
* @return Boolean true if debug mode is enabled, false otherwise
*
* @api
*/
public function isDebug();
/**
* Gets the application root dir.
*
* @return string The application root dir
*
* @api
*/
public function getRootDir();
/**
* Gets the current container.
*
* @return ContainerInterface A ContainerInterface instance
*
* @api
*/
public function getContainer();
/**
* Gets the request start time (not available if debug is disabled).
*
* @return integer The request start timestamp
*
* @api
*/
public function getStartTime();
/**
* Gets the cache directory.
*
* @return string The cache directory
*
* @api
*/
public function getCacheDir();
/**
* Gets the log directory.
*
* @return string The log directory
*
* @api
*/
public function getLogDir();
/**
* Gets the charset of the application.
*
* @return string The charset
*
* @api
*/
public function getCharset();
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-2013 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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\Log;
/**
* DebugLoggerInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface DebugLoggerInterface
{
/**
* Returns an array of logs.
*
* A log is an array with the following mandatory keys:
* timestamp, message, priority, and priorityName.
* It can also have an optional context key containing an array.
*
* @return array An array of logs
*/
public function getLogs();
/**
* Returns the number of errors.
*
* @return integer The number of errors
*/
public function countErrors();
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Log;
use Psr\Log\LoggerInterface as PsrLogger;
/**
* LoggerInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since 2.2, to be removed in 3.0. Type-hint \Psr\Log\LoggerInterface instead.
* @api
*/
interface LoggerInterface extends PsrLogger
{
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use emergency() which is PSR-3 compatible.
*/
public function emerg($message, array $context = array());
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use critical() which is PSR-3 compatible.
*/
public function crit($message, array $context = array());
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use error() which is PSR-3 compatible.
*/
public function err($message, array $context = array());
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use warning() which is PSR-3 compatible.
*/
public function warn($message, array $context = array());
}

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\Log;
use Psr\Log\NullLogger as PsrNullLogger;
/**
* NullLogger.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class NullLogger extends PsrNullLogger implements LoggerInterface
{
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use emergency() which is PSR-3 compatible.
*/
public function emerg($message, array $context = array())
{
}
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use critical() which is PSR-3 compatible.
*/
public function crit($message, array $context = array())
{
}
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use error() which is PSR-3 compatible.
*/
public function err($message, array $context = array())
{
}
/**
* @api
* @deprecated since 2.2, to be removed in 3.0. Use warning() which is PSR-3 compatible.
*/
public function warn($message, array $context = array())
{
}
}

View File

@@ -0,0 +1,308 @@
<?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\Profiler;
/**
* Base Memcache storage for profiling information in a Memcache.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
abstract class BaseMemcacheProfilerStorage implements ProfilerStorageInterface
{
const TOKEN_PREFIX = 'sf_profiler_';
protected $dsn;
protected $lifetime;
/**
* Constructor.
*
* @param string $dsn A data source name
* @param string $username
* @param string $password
* @param int $lifetime The lifetime to use for the purge
*/
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
{
$this->dsn = $dsn;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null)
{
$indexName = $this->getIndexName();
$indexContent = $this->getValue($indexName);
if (!$indexContent) {
return array();
}
$profileList = explode("\n", $indexContent);
$result = array();
foreach ($profileList as $item) {
if ($limit === 0) {
break;
}
if ($item=='') {
continue;
}
list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = explode("\t", $item, 6);
$itemTime = (int) $itemTime;
if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
continue;
}
if (!empty($start) && $itemTime < $start) {
continue;
}
if (!empty($end) && $itemTime > $end) {
continue;
}
$result[$itemToken] = array(
'token' => $itemToken,
'ip' => $itemIp,
'method' => $itemMethod,
'url' => $itemUrl,
'time' => $itemTime,
'parent' => $itemParent,
);
--$limit;
}
usort($result, function($a, $b) {
if ($a['time'] === $b['time']) {
return 0;
}
return $a['time'] > $b['time'] ? -1 : 1;
});
return $result;
}
/**
* {@inheritdoc}
*/
public function purge()
{
// delete only items from index
$indexName = $this->getIndexName();
$indexContent = $this->getValue($indexName);
if (!$indexContent) {
return false;
}
$profileList = explode("\n", $indexContent);
foreach ($profileList as $item) {
if ($item == '') {
continue;
}
if (false !== $pos = strpos($item, "\t")) {
$this->delete($this->getItemName(substr($item, 0, $pos)));
}
}
return $this->delete($indexName);
}
/**
* {@inheritdoc}
*/
public function read($token)
{
if (empty($token)) {
return false;
}
$profile = $this->getValue($this->getItemName($token));
if (false !== $profile) {
$profile = $this->createProfileFromData($token, $profile);
}
return $profile;
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile)
{
$data = array(
'token' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
'data' => $profile->getCollectors(),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime(),
);
$profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken()));
if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime)) {
if (!$profileIndexed) {
// Add to index
$indexName = $this->getIndexName();
$indexRow = implode("\t", array(
$profile->getToken(),
$profile->getIp(),
$profile->getMethod(),
$profile->getUrl(),
$profile->getTime(),
$profile->getParentToken(),
))."\n";
return $this->appendValue($indexName, $indexRow, $this->lifetime);
}
return true;
}
return false;
}
/**
* Retrieve item from the memcache server
*
* @param string $key
*
* @return mixed
*/
abstract protected function getValue($key);
/**
* Store an item on the memcache server under the specified key
*
* @param string $key
* @param mixed $value
* @param int $expiration
*
* @return boolean
*/
abstract protected function setValue($key, $value, $expiration = 0);
/**
* Delete item from the memcache server
*
* @param string $key
*
* @return boolean
*/
abstract protected function delete($key);
/**
* Append data to an existing item on the memcache server
* @param string $key
* @param string $value
* @param int $expiration
*
* @return boolean
*/
abstract protected function appendValue($key, $value, $expiration = 0);
private function createProfileFromData($token, $data, $parent = null)
{
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors($data['data']);
if (!$parent && $data['parent']) {
$parent = $this->read($data['parent']);
}
if ($parent) {
$profile->setParent($parent);
}
foreach ($data['children'] as $token) {
if (!$token) {
continue;
}
if (!$childProfileData = $this->getValue($this->getItemName($token))) {
continue;
}
$profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
}
return $profile;
}
/**
* Get item name
*
* @param string $token
*
* @return string
*/
private function getItemName($token)
{
$name = self::TOKEN_PREFIX.$token;
if ($this->isItemNameValid($name)) {
return $name;
}
return false;
}
/**
* Get name of index
*
* @return string
*/
private function getIndexName()
{
$name = self::TOKEN_PREFIX.'index';
if ($this->isItemNameValid($name)) {
return $name;
}
return false;
}
private function isItemNameValid($name)
{
$length = strlen($name);
if ($length > 250) {
throw new \RuntimeException(sprintf('The memcache item key "%s" is too long (%s bytes). Allowed maximum size is 250 bytes.', $name, $length));
}
return true;
}
}

View File

@@ -0,0 +1,277 @@
<?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\Profiler;
/**
* Storage for profiler using files.
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class FileProfilerStorage implements ProfilerStorageInterface
{
/**
* Folder where profiler data are stored.
*
* @var string
*/
private $folder;
/**
* Constructs the file storage using a "dsn-like" path.
*
* Example : "file:/path/to/the/storage/folder"
*
* @param string $dsn The DSN
*
* @throws \RuntimeException
*/
public function __construct($dsn)
{
if (0 !== strpos($dsn, 'file:')) {
throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn));
}
$this->folder = substr($dsn, 5);
if (!is_dir($this->folder)) {
mkdir($this->folder, 0777, true);
}
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null)
{
$file = $this->getIndexFilename();
if (!file_exists($file)) {
return array();
}
$file = fopen($file, 'r');
fseek($file, 0, SEEK_END);
$result = array();
while (count($result) < $limit && $line = $this->readLineFromFile($file)) {
list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent) = str_getcsv($line);
$csvTime = (int) $csvTime;
if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method)) {
continue;
}
if (!empty($start) && $csvTime < $start) {
continue;
}
if (!empty($end) && $csvTime > $end) {
continue;
}
$result[$csvToken] = array(
'token' => $csvToken,
'ip' => $csvIp,
'method' => $csvMethod,
'url' => $csvUrl,
'time' => $csvTime,
'parent' => $csvParent,
);
}
fclose($file);
return array_values($result);
}
/**
* {@inheritdoc}
*/
public function purge()
{
$flags = \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iterator as $file) {
if (is_file($file)) {
unlink($file);
} else {
rmdir($file);
}
}
}
/**
* {@inheritdoc}
*/
public function read($token)
{
if (!$token || !file_exists($file = $this->getFilename($token))) {
return null;
}
return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile)
{
$file = $this->getFilename($profile->getToken());
$profileIndexed = is_file($file);
if (!$profileIndexed) {
// Create directory
$dir = dirname($file);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
}
// Store profile
$data = array(
'token' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
'data' => $profile->getCollectors(),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime(),
);
if (false === file_put_contents($file, serialize($data))) {
return false;
}
if (!$profileIndexed) {
// Add to index
if (false === $file = fopen($this->getIndexFilename(), 'a')) {
return false;
}
fputcsv($file, array(
$profile->getToken(),
$profile->getIp(),
$profile->getMethod(),
$profile->getUrl(),
$profile->getTime(),
$profile->getParentToken(),
));
fclose($file);
}
return true;
}
/**
* Gets filename to store data, associated to the token.
*
* @param string $token
*
* @return string The profile filename
*/
protected function getFilename($token)
{
// Uses 4 last characters, because first are mostly the same.
$folderA = substr($token, -2, 2);
$folderB = substr($token, -4, 2);
return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
}
/**
* Gets the index filename.
*
* @return string The index filename
*/
protected function getIndexFilename()
{
return $this->folder.'/index.csv';
}
/**
* Reads a line in the file, backward.
*
* This function automatically skips the empty lines and do not include the line return in result value.
*
* @param resource $file The file resource, with the pointer placed at the end of the line to read
*
* @return mixed A string representing the line or null if beginning of file is reached
*/
protected function readLineFromFile($file)
{
$line = '';
$position = ftell($file);
if (0 === $position) {
return null;
}
while (true) {
$chunkSize = min($position, 1024);
$position -= $chunkSize;
fseek($file, $position);
if (0 === $chunkSize) {
// bof reached
break;
}
$buffer = fread($file, $chunkSize);
if (false === ($upTo = strrpos($buffer, "\n"))) {
$line = $buffer.$line;
continue;
}
$position += $upTo;
$line = substr($buffer, $upTo + 1).$line;
fseek($file, max(0, $position), SEEK_SET);
if ('' !== $line) {
break;
}
}
return '' === $line ? null : $line;
}
protected function createProfileFromData($token, $data, $parent = null)
{
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors($data['data']);
if (!$parent && $data['parent']) {
$parent = $this->read($data['parent']);
}
if ($parent) {
$profile->setParent($parent);
}
foreach ($data['children'] as $token) {
if (!$token || !file_exists($file = $this->getFilename($token))) {
continue;
}
$profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
}
return $profile;
}
}

View File

@@ -0,0 +1,110 @@
<?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\Profiler;
use Memcache;
/**
* Memcache Profiler Storage
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class MemcacheProfilerStorage extends BaseMemcacheProfilerStorage
{
/**
* @var Memcache
*/
private $memcache;
/**
* Internal convenience method that returns the instance of the Memcache
*
* @return Memcache
*
* @throws \RuntimeException
*/
protected function getMemcache()
{
if (null === $this->memcache) {
if (!preg_match('#^memcache://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) {
throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcache with an invalid dsn "%s". The expected format is "memcache://[host]:port".', $this->dsn));
}
$host = $matches[1] ?: $matches[2];
$port = $matches[3];
$memcache = new Memcache;
$memcache->addServer($host, $port);
$this->memcache = $memcache;
}
return $this->memcache;
}
/**
* Set instance of the Memcache
*
* @param Memcache $memcache
*/
public function setMemcache($memcache)
{
$this->memcache = $memcache;
}
/**
* {@inheritdoc}
*/
protected function getValue($key)
{
return $this->getMemcache()->get($key);
}
/**
* {@inheritdoc}
*/
protected function setValue($key, $value, $expiration = 0)
{
return $this->getMemcache()->set($key, $value, false, time() + $expiration);
}
/**
* {@inheritdoc}
*/
protected function delete($key)
{
return $this->getMemcache()->delete($key);
}
/**
* {@inheritdoc}
*/
protected function appendValue($key, $value, $expiration = 0)
{
$memcache = $this->getMemcache();
if (method_exists($memcache, 'append')) {
//Memcache v3.0
if (!$result = $memcache->append($key, $value, false, $expiration)) {
return $memcache->set($key, $value, false, $expiration);
}
return $result;
}
//simulate append in Memcache <3.0
$content = $memcache->get($key);
return $memcache->set($key, $content.$value, false, $expiration);
}
}

View File

@@ -0,0 +1,105 @@
<?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\Profiler;
use Memcached;
/**
* Memcached Profiler Storage
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class MemcachedProfilerStorage extends BaseMemcacheProfilerStorage
{
/**
* @var Memcached
*/
private $memcached;
/**
* Internal convenience method that returns the instance of the Memcached
*
* @return Memcached
*
* @throws \RuntimeException
*/
protected function getMemcached()
{
if (null === $this->memcached) {
if (!preg_match('#^memcached://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) {
throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcached with an invalid dsn "%s". The expected format is "memcached://[host]:port".', $this->dsn));
}
$host = $matches[1] ?: $matches[2];
$port = $matches[3];
$memcached = new Memcached;
//disable compression to allow appending
$memcached->setOption(Memcached::OPT_COMPRESSION, false);
$memcached->addServer($host, $port);
$this->memcached = $memcached;
}
return $this->memcached;
}
/**
* Set instance of the Memcached
*
* @param Memcached $memcached
*/
public function setMemcached($memcached)
{
$this->memcached = $memcached;
}
/**
* {@inheritdoc}
*/
protected function getValue($key)
{
return $this->getMemcached()->get($key);
}
/**
* {@inheritdoc}
*/
protected function setValue($key, $value, $expiration = 0)
{
return $this->getMemcached()->set($key, $value, time() + $expiration);
}
/**
* {@inheritdoc}
*/
protected function delete($key)
{
return $this->getMemcached()->delete($key);
}
/**
* {@inheritdoc}
*/
protected function appendValue($key, $value, $expiration = 0)
{
$memcached = $this->getMemcached();
if (!$result = $memcached->append($key, $value)) {
return $memcached->set($key, $value, $expiration);
}
return $result;
}
}

View File

@@ -0,0 +1,236 @@
<?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\Profiler;
class MongoDbProfilerStorage implements ProfilerStorageInterface
{
protected $dsn;
protected $lifetime;
private $mongo;
/**
* Constructor.
*
* @param string $dsn A data source name
* @param string $username Not used
* @param string $password Not used
* @param integer $lifetime The lifetime to use for the purge
*/
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
{
$this->dsn = $dsn;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null)
{
$cursor = $this->getMongo()->find($this->buildQuery($ip, $url, $method, $start, $end), array('_id', 'parent', 'ip', 'method', 'url', 'time'))->sort(array('time' => -1))->limit($limit);
$tokens = array();
foreach ($cursor as $profile) {
$tokens[] = $this->getData($profile);
}
return $tokens;
}
/**
* {@inheritdoc}
*/
public function purge()
{
$this->getMongo()->remove(array());
}
/**
* {@inheritdoc}
*/
public function read($token)
{
$profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true)));
if (null !== $profile) {
$profile = $this->createProfileFromData($this->getData($profile));
}
return $profile;
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile)
{
$this->cleanup();
$record = array(
'_id' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'data' => base64_encode(serialize($profile->getCollectors())),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime()
);
$result = $this->getMongo()->update(array('_id' => $profile->getToken()), array_filter($record, function ($v) { return !empty($v); }), array('upsert' => true));
return (boolean) (isset($result['ok']) ? $result['ok'] : $result);
}
/**
* Internal convenience method that returns the instance of the MongoDB Collection
*
* @return \MongoCollection
*
* @throws \RuntimeException
*/
protected function getMongo()
{
if ($this->mongo === null) {
if (preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $this->dsn, $matches)) {
$server = $matches[1].(!empty($matches[2]) ? '/'.$matches[2] : '');
$database = $matches[2];
$collection = $matches[3];
$mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient';
$mongo = new $mongoClass($server);
$this->mongo = $mongo->selectCollection($database, $collection);
} else {
throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn));
}
}
return $this->mongo;
}
/**
* @param array $data
*
* @return Profile
*/
protected function createProfileFromData(array $data)
{
$profile = $this->getProfile($data);
if ($data['parent']) {
$parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true)));
if ($parent) {
$profile->setParent($this->getProfile($this->getData($parent)));
}
}
$profile->setChildren($this->readChildren($data['token']));
return $profile;
}
/**
* @param string $token
*
* @return Profile[] An array of Profile instances
*/
protected function readChildren($token)
{
$profiles = array();
$cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true)));
foreach ($cursor as $d) {
$profiles[] = $this->getProfile($this->getData($d));
}
return $profiles;
}
protected function cleanup()
{
$this->getMongo()->remove(array('time' => array('$lt' => time() - $this->lifetime)));
}
/**
* @param string $ip
* @param string $url
* @param string $method
* @param int $start
* @param int $end
*
* @return array
*/
private function buildQuery($ip, $url, $method, $start, $end)
{
$query = array();
if (!empty($ip)) {
$query['ip'] = $ip;
}
if (!empty($url)) {
$query['url'] = $url;
}
if (!empty($method)) {
$query['method'] = $method;
}
if (!empty($start) || !empty($end)) {
$query['time'] = array();
}
if (!empty($start)) {
$query['time']['$gte'] = $start;
}
if (!empty($end)) {
$query['time']['$lte'] = $end;
}
return $query;
}
/**
* @param array $data
*
* @return array
*/
private function getData(array $data)
{
return array(
'token' => $data['_id'],
'parent' => isset($data['parent']) ? $data['parent'] : null,
'ip' => isset($data['ip']) ? $data['ip'] : null,
'method' => isset($data['method']) ? $data['method'] : null,
'url' => isset($data['url']) ? $data['url'] : null,
'time' => isset($data['time']) ? $data['time'] : null,
'data' => isset($data['data']) ? $data['data'] : null,
);
}
/**
* @param array $data
*
* @return Profile
*/
private function getProfile(array $data)
{
$profile = new Profile($data['token']);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors(unserialize(base64_decode($data['data'])));
return $profile;
}
}

View File

@@ -0,0 +1,79 @@
<?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\Profiler;
/**
* A ProfilerStorage for Mysql
*
* @author Jan Schumann <js@schumann-it.com>
*/
class MysqlProfilerStorage extends PdoProfilerStorage
{
/**
* {@inheritdoc}
*/
protected function initDb()
{
if (null === $this->db) {
if (0 !== strpos($this->dsn, 'mysql')) {
throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Mysql with an invalid dsn "%s". The expected format is "mysql:dbname=database_name;host=host_name".', $this->dsn));
}
if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
throw new \RuntimeException('You need to enable PDO_Mysql extension for the profiler to run properly.');
}
$db = new \PDO($this->dsn, $this->username, $this->password);
$db->exec('CREATE TABLE IF NOT EXISTS sf_profiler_data (token VARCHAR(255) PRIMARY KEY, data LONGTEXT, ip VARCHAR(64), method VARCHAR(6), url VARCHAR(255), time INTEGER UNSIGNED, parent VARCHAR(255), created_at INTEGER UNSIGNED, KEY (created_at), KEY (ip), KEY (method), KEY (url), KEY (parent))');
$this->db = $db;
}
return $this->db;
}
/**
* {@inheritdoc}
*/
protected function buildCriteria($ip, $url, $start, $end, $limit, $method)
{
$criteria = array();
$args = array();
if ($ip = preg_replace('/[^\d\.]/', '', $ip)) {
$criteria[] = 'ip LIKE :ip';
$args[':ip'] = '%'.$ip.'%';
}
if ($url) {
$criteria[] = 'url LIKE :url';
$args[':url'] = '%'.addcslashes($url, '%_\\').'%';
}
if ($method) {
$criteria[] = 'method = :method';
$args[':method'] = $method;
}
if (!empty($start)) {
$criteria[] = 'time >= :start';
$args[':start'] = $start;
}
if (!empty($end)) {
$criteria[] = 'time <= :end';
$args[':end'] = $end;
}
return array($criteria, $args);
}
}

View File

@@ -0,0 +1,264 @@
<?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\Profiler;
/**
* Base PDO storage for profiling information in a PDO database.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jan Schumann <js@schumann-it.com>
*/
abstract class PdoProfilerStorage implements ProfilerStorageInterface
{
protected $dsn;
protected $username;
protected $password;
protected $lifetime;
protected $db;
/**
* Constructor.
*
* @param string $dsn A data source name
* @param string $username The username for the database
* @param string $password The password for the database
* @param integer $lifetime The lifetime to use for the purge
*/
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
{
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null)
{
if (null === $start) {
$start = 0;
}
if (null === $end) {
$end = time();
}
list($criteria, $args) = $this->buildCriteria($ip, $url, $start, $end, $limit, $method);
$criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : '';
$db = $this->initDb();
$tokens = $this->fetch($db, 'SELECT token, ip, method, url, time, parent FROM sf_profiler_data '.$criteria.' ORDER BY time DESC LIMIT '.((integer) $limit), $args);
$this->close($db);
return $tokens;
}
/**
* {@inheritdoc}
*/
public function read($token)
{
$db = $this->initDb();
$args = array(':token' => $token);
$data = $this->fetch($db, 'SELECT data, parent, ip, method, url, time FROM sf_profiler_data WHERE token = :token LIMIT 1', $args);
$this->close($db);
if (isset($data[0]['data'])) {
return $this->createProfileFromData($token, $data[0]);
}
return null;
}
/**
* {@inheritdoc}
*/
public function write(Profile $profile)
{
$db = $this->initDb();
$args = array(
':token' => $profile->getToken(),
':parent' => $profile->getParentToken(),
':data' => base64_encode(serialize($profile->getCollectors())),
':ip' => $profile->getIp(),
':method' => $profile->getMethod(),
':url' => $profile->getUrl(),
':time' => $profile->getTime(),
':created_at' => time(),
);
try {
if ($this->has($profile->getToken())) {
$this->exec($db, 'UPDATE sf_profiler_data SET parent = :parent, data = :data, ip = :ip, method = :method, url = :url, time = :time, created_at = :created_at WHERE token = :token', $args);
} else {
$this->exec($db, 'INSERT INTO sf_profiler_data (token, parent, data, ip, method, url, time, created_at) VALUES (:token, :parent, :data, :ip, :method, :url, :time, :created_at)', $args);
}
$this->cleanup();
$status = true;
} catch (\Exception $e) {
$status = false;
}
$this->close($db);
return $status;
}
/**
* {@inheritdoc}
*/
public function purge()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM sf_profiler_data');
$this->close($db);
}
/**
* Build SQL criteria to fetch records by ip and url
*
* @param string $ip The IP
* @param string $url The URL
* @param string $start The start period to search from
* @param string $end The end period to search to
* @param string $limit The maximum number of tokens to return
* @param string $method The request method
*
* @return array An array with (criteria, args)
*/
abstract protected function buildCriteria($ip, $url, $start, $end, $limit, $method);
/**
* Initializes the database
*
* @throws \RuntimeException When the requested database driver is not installed
*/
abstract protected function initDb();
protected function cleanup()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM sf_profiler_data WHERE created_at < :time', array(':time' => time() - $this->lifetime));
$this->close($db);
}
protected function exec($db, $query, array $args = array())
{
$stmt = $this->prepareStatement($db, $query);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$success = $stmt->execute();
if (!$success) {
throw new \RuntimeException(sprintf('Error executing query "%s"', $query));
}
}
protected function prepareStatement($db, $query)
{
try {
$stmt = $db->prepare($query);
} catch (\Exception $e) {
$stmt = false;
}
if (false === $stmt) {
throw new \RuntimeException('The database cannot successfully prepare the statement');
}
return $stmt;
}
protected function fetch($db, $query, array $args = array())
{
$stmt = $this->prepareStatement($db, $query);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$stmt->execute();
$return = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return $return;
}
protected function close($db)
{
}
protected function createProfileFromData($token, $data, $parent = null)
{
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setCollectors(unserialize(base64_decode($data['data'])));
if (!$parent && !empty($data['parent'])) {
$parent = $this->read($data['parent']);
}
if ($parent) {
$profile->setParent($parent);
}
$profile->setChildren($this->readChildren($token, $profile));
return $profile;
}
/**
* Reads the child profiles for the given token.
*
* @param string $token The parent token
* @param string $parent The parent instance
*
* @return Profile[] An array of Profile instance
*/
protected function readChildren($token, $parent)
{
$db = $this->initDb();
$data = $this->fetch($db, 'SELECT token, data, ip, method, url, time FROM sf_profiler_data WHERE parent = :token', array(':token' => $token));
$this->close($db);
if (!$data) {
return array();
}
$profiles = array();
foreach ($data as $d) {
$profiles[] = $this->createProfileFromData($d['token'], $d, $parent);
}
return $profiles;
}
/**
* Returns whether data for the given token already exists in storage.
*
* @param string $token The profile token
*
* @return string
*/
protected function has($token)
{
$db = $this->initDb();
$tokenExists = $this->fetch($db, 'SELECT 1 FROM sf_profiler_data WHERE token = :token LIMIT 1', array(':token' => $token));
$this->close($db);
return !empty($tokenExists);
}
}

View File

@@ -0,0 +1,275 @@
<?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\Profiler;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* Profile.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Profile
{
private $token;
/**
* @var DataCollectorInterface[]
*/
private $collectors = array();
private $ip;
private $method;
private $url;
private $time;
/**
* @var Profile
*/
private $parent;
/**
* @var Profile[]
*/
private $children = array();
/**
* Constructor.
*
* @param string $token The token
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Sets the token.
*
* @param string $token The token
*/
public function setToken($token)
{
$this->token = $token;
}
/**
* Gets the token.
*
* @return string The token
*/
public function getToken()
{
return $this->token;
}
/**
* Sets the parent token
*
* @param Profile $parent The parent Profile
*/
public function setParent(Profile $parent)
{
$this->parent = $parent;
}
/**
* Returns the parent profile.
*
* @return Profile The parent profile
*/
public function getParent()
{
return $this->parent;
}
/**
* Returns the parent token.
*
* @return null|string The parent token
*/
public function getParentToken()
{
return $this->parent ? $this->parent->getToken() : null;
}
/**
* Returns the IP.
*
* @return string The IP
*/
public function getIp()
{
return $this->ip;
}
/**
* Sets the IP.
*
* @param string $ip
*/
public function setIp($ip)
{
$this->ip = $ip;
}
/**
* Returns the request method.
*
* @return string The request method
*/
public function getMethod()
{
return $this->method;
}
public function setMethod($method)
{
$this->method = $method;
}
/**
* Returns the URL.
*
* @return string The URL
*/
public function getUrl()
{
return $this->url;
}
public function setUrl($url)
{
$this->url = $url;
}
/**
* Returns the time.
*
* @return string The time
*/
public function getTime()
{
if (null === $this->time) {
return 0;
}
return $this->time;
}
public function setTime($time)
{
$this->time = $time;
}
/**
* Finds children profilers.
*
* @return Profile[] An array of Profile
*/
public function getChildren()
{
return $this->children;
}
/**
* Sets children profiler.
*
* @param Profile[] $children An array of Profile
*/
public function setChildren(array $children)
{
$this->children = array();
foreach ($children as $child) {
$this->addChild($child);
}
}
/**
* Adds the child token
*
* @param Profile $child The child Profile
*/
public function addChild(Profile $child)
{
$this->children[] = $child;
$child->setParent($this);
}
/**
* Gets a Collector by name.
*
* @param string $name A collector name
*
* @return DataCollectorInterface A DataCollectorInterface instance
*
* @throws \InvalidArgumentException if the collector does not exist
*/
public function getCollector($name)
{
if (!isset($this->collectors[$name])) {
throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name));
}
return $this->collectors[$name];
}
/**
* Gets the Collectors associated with this profile.
*
* @return DataCollectorInterface[]
*/
public function getCollectors()
{
return $this->collectors;
}
/**
* Sets the Collectors associated with this profile.
*
* @param DataCollectorInterface[] $collectors
*/
public function setCollectors(array $collectors)
{
$this->collectors = array();
foreach ($collectors as $collector) {
$this->addCollector($collector);
}
}
/**
* Adds a Collector.
*
* @param DataCollectorInterface $collector A DataCollectorInterface instance
*/
public function addCollector(DataCollectorInterface $collector)
{
$this->collectors[$collector->getName()] = $collector;
}
/**
* Returns true if a Collector for the given name exists.
*
* @param string $name A collector name
*
* @return Boolean
*/
public function hasCollector($name)
{
return isset($this->collectors[$name]);
}
public function __sleep()
{
return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time');
}
}

View File

@@ -0,0 +1,287 @@
<?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\Profiler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Psr\Log\LoggerInterface;
/**
* Profiler.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Profiler
{
/**
* @var ProfilerStorageInterface
*/
private $storage;
/**
* @var DataCollectorInterface[]
*/
private $collectors = array();
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var Boolean
*/
private $enabled = true;
/**
* Constructor.
*
* @param ProfilerStorageInterface $storage A ProfilerStorageInterface instance
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null)
{
$this->storage = $storage;
$this->logger = $logger;
}
/**
* Disables the profiler.
*/
public function disable()
{
$this->enabled = false;
}
/**
* Enables the profiler.
*/
public function enable()
{
$this->enabled = true;
}
/**
* Loads the Profile for the given Response.
*
* @param Response $response A Response instance
*
* @return Profile A Profile instance
*/
public function loadProfileFromResponse(Response $response)
{
if (!$token = $response->headers->get('X-Debug-Token')) {
return false;
}
return $this->loadProfile($token);
}
/**
* Loads the Profile for the given token.
*
* @param string $token A token
*
* @return Profile A Profile instance
*/
public function loadProfile($token)
{
return $this->storage->read($token);
}
/**
* Saves a Profile.
*
* @param Profile $profile A Profile instance
*
* @return Boolean
*/
public function saveProfile(Profile $profile)
{
if (!($ret = $this->storage->write($profile)) && null !== $this->logger) {
$this->logger->warning('Unable to store the profiler information.');
}
return $ret;
}
/**
* Purges all data from the storage.
*/
public function purge()
{
$this->storage->purge();
}
/**
* Exports the current profiler data.
*
* @param Profile $profile A Profile instance
*
* @return string The exported data
*/
public function export(Profile $profile)
{
return base64_encode(serialize($profile));
}
/**
* Imports data into the profiler storage.
*
* @param string $data A data string as exported by the export() method
*
* @return Profile A Profile instance
*/
public function import($data)
{
$profile = unserialize(base64_decode($data));
if ($this->storage->read($profile->getToken())) {
return false;
}
$this->saveProfile($profile);
return $profile;
}
/**
* Finds profiler tokens for the given criteria.
*
* @param string $ip The IP
* @param string $url The URL
* @param string $limit The maximum number of tokens to return
* @param string $method The request method
* @param string $start The start date to search from
* @param string $end The end date to search to
*
* @return array An array of tokens
*
* @see http://fr2.php.net/manual/en/datetime.formats.php for the supported date/time formats
*/
public function find($ip, $url, $limit, $method, $start, $end)
{
if ('' != $start && null !== $start) {
$start = new \DateTime($start);
$start = $start->getTimestamp();
} else {
$start = null;
}
if ('' != $end && null !== $end) {
$end = new \DateTime($end);
$end = $end->getTimestamp();
} else {
$end = null;
}
return $this->storage->find($ip, $url, $limit, $method, $start, $end);
}
/**
* Collects data for the given Response.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
* @param \Exception $exception An exception instance if the request threw one
*
* @return Profile|null A Profile instance or null if the profiler is disabled
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
if (false === $this->enabled) {
return;
}
$profile = new Profile(substr(sha1(uniqid(mt_rand(), true)), 0, 6));
$profile->setTime(time());
$profile->setUrl($request->getUri());
$profile->setIp($request->getClientIp());
$profile->setMethod($request->getMethod());
$response->headers->set('X-Debug-Token', $profile->getToken());
foreach ($this->collectors as $collector) {
$collector->collect($request, $response, $exception);
// forces collectors to become "read/only" (they loose their object dependencies)
$profile->addCollector(unserialize(serialize($collector)));
}
return $profile;
}
/**
* Gets the Collectors associated with this profiler.
*
* @return array An array of collectors
*/
public function all()
{
return $this->collectors;
}
/**
* Sets the Collectors associated with this profiler.
*
* @param DataCollectorInterface[] $collectors An array of collectors
*/
public function set(array $collectors = array())
{
$this->collectors = array();
foreach ($collectors as $collector) {
$this->add($collector);
}
}
/**
* Adds a Collector.
*
* @param DataCollectorInterface $collector A DataCollectorInterface instance
*/
public function add(DataCollectorInterface $collector)
{
$this->collectors[$collector->getName()] = $collector;
}
/**
* Returns true if a Collector for the given name exists.
*
* @param string $name A collector name
*
* @return Boolean
*/
public function has($name)
{
return isset($this->collectors[$name]);
}
/**
* Gets a Collector by name.
*
* @param string $name A collector name
*
* @return DataCollectorInterface A DataCollectorInterface instance
*
* @throws \InvalidArgumentException if the collector does not exist
*/
public function get($name)
{
if (!isset($this->collectors[$name])) {
throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name));
}
return $this->collectors[$name];
}
}

View File

@@ -0,0 +1,59 @@
<?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\Profiler;
/**
* ProfilerStorageInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ProfilerStorageInterface
{
/**
* Finds profiler tokens for the given criteria.
*
* @param string $ip The IP
* @param string $url The URL
* @param string $limit The maximum number of tokens to return
* @param string $method The request method
* @param int|null $start The start date to search from
* @param int|null $end The end date to search to
*
* @return array An array of tokens
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null);
/**
* Reads data associated with the given token.
*
* The method returns false if the token does not exist in the storage.
*
* @param string $token A token
*
* @return Profile The profile associated with token
*/
public function read($token);
/**
* Saves a Profile.
*
* @param Profile $profile A Profile instance
*
* @return Boolean Write operation successful
*/
public function write(Profile $profile);
/**
* Purges all data from the database.
*/
public function purge();
}

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