diff options
| author | Greg Roach <fisharebest@gmail.com> | 2018-01-19 15:40:08 +0000 |
|---|---|---|
| committer | Greg Roach <fisharebest@gmail.com> | 2018-01-19 22:39:48 +0000 |
| commit | 9499ed38534b88c310649782326f430b724fbe3a (patch) | |
| tree | 3b10195a8e8e56024f75403869d5a56074314181 /vendor/symfony/http-kernel | |
| parent | c9b4efa1714b4374cc3446e0d3a1935da0cb3a59 (diff) | |
| download | webtrees-9499ed38534b88c310649782326f430b724fbe3a.tar.gz webtrees-9499ed38534b88c310649782326f430b724fbe3a.tar.bz2 webtrees-9499ed38534b88c310649782326f430b724fbe3a.zip | |
Add symfony/http-kernel
Diffstat (limited to 'vendor/symfony/http-kernel')
251 files changed, 24788 insertions, 0 deletions
diff --git a/vendor/symfony/http-kernel/.gitignore b/vendor/symfony/http-kernel/.gitignore new file mode 100644 index 0000000000..94a6a25288 --- /dev/null +++ b/vendor/symfony/http-kernel/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +Tests/Fixtures/cache/ +Tests/Fixtures/logs/ diff --git a/vendor/symfony/http-kernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Bundle/Bundle.php new file mode 100644 index 0000000000..0b0ea088dc --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,231 @@ +<?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\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Console\Application; +use Symfony\Component\Finder\Finder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * An implementation of BundleInterface that adds a few conventions + * for DependencyInjection extensions and Console commands. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class Bundle implements BundleInterface +{ + use ContainerAwareTrait; + + protected $name; + protected $extension; + protected $path; + private $namespace; + + /** + * 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 + */ + public function getContainerExtension() + { + if (null === $this->extension) { + $extension = $this->createContainerExtension(); + + if (null !== $extension) { + if (!$extension instanceof ExtensionInterface) { + throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_class($extension))); + } + + // check naming convention + $basename = preg_replace('/Bundle$/', '', $this->getName()); + $expectedAlias = Container::underscore($basename); + + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf( + 'Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', + $expectedAlias, $extension->getAlias() + )); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + if ($this->extension) { + return $this->extension; + } + } + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace() + { + if (null === $this->namespace) { + $this->parseClassName(); + } + + return $this->namespace; + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + */ + public function getPath() + { + if (null === $this->path) { + $reflected = new \ReflectionObject($this); + $this->path = dirname($reflected->getFileName()); + } + + return $this->path; + } + + /** + * Returns the bundle parent name. + * + * @return string|null The Bundle parent name it overrides or null if no parent + */ + public function getParent() + { + } + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + final public function getName() + { + if (null === $this->name) { + $this->parseClassName(); + } + + return $this->name; + } + + /** + * Finds and registers Commands. + * + * Override this method if your bundle commands do not follow the conventions: + * + * * Commands are in the 'Command' sub-directory + * * Commands extend Symfony\Component\Console\Command\Command + * + * @param Application $application An Application instance + */ + public function registerCommands(Application $application) + { + if (!is_dir($dir = $this->getPath().'/Command')) { + return; + } + + if (!class_exists('Symfony\Component\Finder\Finder')) { + throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($dir); + + $prefix = $this->getNamespace().'\\Command'; + foreach ($finder as $file) { + $ns = $prefix; + if ($relativePath = $file->getRelativePath()) { + $ns .= '\\'.str_replace('/', '\\', $relativePath); + } + $class = $ns.'\\'.$file->getBasename('.php'); + if ($this->container) { + $commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : array(); + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if (isset($commandIds[$alias]) || $this->container->has($alias)) { + continue; + } + } + $r = new \ReflectionClass($class); + if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { + $application->add($r->newInstance()); + } + } + } + + /** + * Returns the bundle's container extension class. + * + * @return string + */ + protected function getContainerExtensionClass() + { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + } + + /** + * Creates the bundle's container extension. + * + * @return ExtensionInterface|null + */ + protected function createContainerExtension() + { + if (class_exists($class = $this->getContainerExtensionClass())) { + return new $class(); + } + } + + private function parseClassName() + { + $pos = strrpos(static::class, '\\'); + $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); + if (null === $this->name) { + $this->name = false === $pos ? static::class : substr(static::class, $pos + 1); + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Bundle/BundleInterface.php new file mode 100644 index 0000000000..25eea1d76d --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleInterface.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface BundleInterface extends ContainerAwareInterface +{ + /** + * Boots the Bundle. + */ + public function boot(); + + /** + * Shutdowns the Bundle. + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + * + * @return ExtensionInterface|null The default extension or null if there is none + */ + public function getContainerExtension(); + + /** + * Returns the bundle name that this bundle overrides. + * + * Despite its name, this method does not imply any parent/child relationship + * between the bundles, just a way to extend and override an existing + * bundle. + * + * @return string The Bundle name it overrides or null if no parent + */ + public function getParent(); + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + public function getName(); + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace(); + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + * + * @return string The Bundle absolute path + */ + public function getPath(); +} diff --git a/vendor/symfony/http-kernel/CHANGELOG.md b/vendor/symfony/http-kernel/CHANGELOG.md new file mode 100644 index 0000000000..061f61d172 --- /dev/null +++ b/vendor/symfony/http-kernel/CHANGELOG.md @@ -0,0 +1,134 @@ +CHANGELOG +========= + +3.3.0 +----- + + * added `kernel.project_dir` and `Kernel::getProjectDir()` + * deprecated `kernel.root_dir` and `Kernel::getRootDir()` + * deprecated `Kernel::getEnvParameters()` + * deprecated the special `SYMFONY__` environment variables + * added the possibility to change the query string parameter used by `UriSigner` + * deprecated `LazyLoadingFragmentHandler::addRendererService()` + * deprecated `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * deprecated `Psr6CacheClearer::addPool()` + +3.2.0 +----- + + * deprecated `DataCollector::varToString()`, use `cloneVar()` instead + * changed surrogate capability name in `AbstractSurrogate::addSurrogateCapability` to 'symfony' + * Added `ControllerArgumentValueResolverPass` + +3.1.0 +----- + * deprecated passing objects as URI attributes to the ESI and SSI renderers + * deprecated `ControllerResolver::getArguments()` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolver` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getMethod()` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getRedirect()` + * added the `kernel.controller_arguments` event, triggered after controller arguments have been resolved + +3.0.0 +----- + + * removed `Symfony\Component\HttpKernel\Kernel::init()` + * removed `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle()` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle()` + * removed `Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher::setProfiler()` + * removed `Symfony\Component\HttpKernel\EventListener\FragmentListener::getLocalIpAddresses()` + * removed `Symfony\Component\HttpKernel\EventListener\LocaleListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\RouterListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest()` + * removed `Symfony\Component\HttpKernel\Fragment\FragmentHandler::setRequest()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::hasSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::addSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::needsEsiParsing()` + * removed `Symfony\Component\HttpKernel\HttpCache\HttpCache::getEsi()` + * removed `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * removed `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * removed `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` + * removed `Symfony\Component\HttpKernel\EventListener\EsiListener` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategy` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategyInterface` + * removed `Symfony\Component\HttpKernel\Log\LoggerInterface` + * removed `Symfony\Component\HttpKernel\Log\NullLogger` + * removed `Symfony\Component\HttpKernel\Profiler::import()` + * removed `Symfony\Component\HttpKernel\Profiler::export()` + +2.8.0 +----- + + * deprecated `Profiler::import` and `Profiler::export` + +2.7.0 +----- + + * added the HTTP status code to profiles + +2.6.0 +----- + + * deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead + * deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle` + +2.5.0 +----- + + * deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead + +2.4.0 +----- + + * added event listeners for the session + * added the KernelEvents::FINISH_REQUEST event + +2.3.0 +----- + + * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()` + * added the possibility to specify an id an extra attributes to hinclude tags + * added the collect of data if a controller is a Closure in the Request collector + * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more + detailed messages + +2.2.0 +----- + + * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) + * added Symfony\Component\HttpKernel\EventListener\FragmentListener + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) + * [BC BREAK] renamed TimeDataCollector::getTotalTime() to + TimeDataCollector::getDuration() + * updated the MemoryDataCollector to include the memory used in the + kernel.terminate event listeners + * moved the Stopwatch classes to a new component + * added TraceableControllerResolver + * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) + * added support for WinCache opcode cache in ConfigDataCollector + +2.1.0 +----- + + * [BC BREAK] the charset is now configured via the Kernel::getCharset() method + * [BC BREAK] the current locale for the user is not stored anymore in the session + * added the HTTP method to the profiler storage + * updated all listeners to implement EventSubscriberInterface + * added TimeDataCollector + * added ContainerAwareTraceableEventDispatcher + * moved TraceableEventDispatcherInterface to the EventDispatcher component + * added RouterListener, LocaleListener, and StreamedResponseListener + * added CacheClearerInterface (and ChainCacheClearer) + * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) + * added a Stopwatch class + * added WarmableInterface + * improved extensibility between bundles + * added profiler storages for Memcache(d), File-based, MongoDB, Redis + * moved Filesystem class to its own component diff --git a/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 0000000000..675c584234 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -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); +} diff --git a/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 0000000000..c749c7c0a4 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,55 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich <ddobervich@gmail.com> + */ +class ChainCacheClearer implements CacheClearerInterface +{ + /** + * @var array + */ + protected $clearers; + + /** + * Constructs a new instance of ChainCacheClearer. + * + * @param array $clearers The initial clearers + */ + public function __construct(array $clearers = array()) + { + $this->clearers = $clearers; + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } + + /** + * Adds a cache clearer to the aggregate. + * + * @param CacheClearerInterface $clearer + */ + public function add(CacheClearerInterface $clearer) + { + $this->clearers[] = $clearer; + } +} diff --git a/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php new file mode 100644 index 0000000000..2336b18a29 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class Psr6CacheClearer implements CacheClearerInterface +{ + private $pools = array(); + + public function __construct(array $pools = array()) + { + $this->pools = $pools; + } + + public function addPool(CacheItemPoolInterface $pool) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), E_USER_DEPRECATED); + + $this->pools[] = $pool; + } + + public function hasPool($name) + { + return isset($this->pools[$name]); + } + + public function clearPool($name) + { + if (!isset($this->pools[$name])) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name)); + } + + return $this->pools[$name]->clear(); + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->pools as $pool) { + $pool->clear(); + } + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 0000000000..dba35a639a --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -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)); + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 0000000000..e5f4e4fa4a --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,74 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + protected $warmers = array(); + protected $optionalsEnabled = false; + + public function __construct(array $warmers = array()) + { + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function enableOptionalWarmers() + { + $this->optionalsEnabled = true; + } + + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); + } + } + + /** + * Checks whether this warmer is optional or not. + * + * @return bool always false + */ + public function isOptional() + { + return false; + } + + public function setWarmers(array $warmers) + { + $this->warmers = array(); + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function add(CacheWarmerInterface $warmer) + { + $this->warmers[] = $warmer; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 0000000000..8fece5e954 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + * + * @return bool true if the warmer is optional, false otherwise + */ + public function isOptional(); +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 0000000000..25d8ee8f61 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -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); +} diff --git a/vendor/symfony/http-kernel/Client.php b/vendor/symfony/http-kernel/Client.php new file mode 100644 index 0000000000..94f70cd6f7 --- /dev/null +++ b/vendor/symfony/http-kernel/Client.php @@ -0,0 +1,206 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @method Request|null getRequest() A Request instance + * @method Response|null getResponse() A Response instance + */ +class Client extends BaseClient +{ + protected $kernel; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel An HttpKernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->kernel = $kernel; + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * 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)); + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = dirname(dirname($r->getFileName())).'/autoload.php'; + if (file_exists($file)) { + $requires .= "require_once '".str_replace("'", "\\'", $file)."';\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $code = <<<EOF +<?php + +error_reporting($errorReporting); + +$requires + +\$kernel = unserialize('$kernel'); +\$request = unserialize('$request'); +EOF; + + return $code.$this->getHandleScript(); + } + + protected function getHandleScript() + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + /** + * Converts the BrowserKit request to a HttpKernel request. + * + * @param DomRequest $request A DomRequest instance + * + * @return Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + * + * @param array $files An array of files + * + * @return array An array with all uploaded files marked as already moved + */ + protected function filterFiles(array $files) + { + $filtered = array(); + foreach ($files as $key => $value) { + if (is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + 0, + UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getClientSize(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * Converts the HttpKernel response to a BrowserKit response. + * + * @param Response $response A Response instance + * + * @return DomResponse A DomResponse instance + */ + protected function filterResponse($response) + { + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); + } +} diff --git a/vendor/symfony/http-kernel/Config/EnvParametersResource.php b/vendor/symfony/http-kernel/Config/EnvParametersResource.php new file mode 100644 index 0000000000..0fe6aca8c1 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/EnvParametersResource.php @@ -0,0 +1,99 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + +/** + * EnvParametersResource represents resources stored in prefixed environment variables. + * + * @author Chris Wilkinson <chriswilkinson84@gmail.com> + */ +class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable +{ + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $variables; + + /** + * Constructor. + * + * @param string $prefix + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->variables = $this->findVariables(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return serialize($this->getResource()); + } + + /** + * @return array An array with two keys: 'prefix' for the prefix used and 'variables' containing all the variables watched by this resource + */ + public function getResource() + { + return array('prefix' => $this->prefix, 'variables' => $this->variables); + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + return $this->findVariables() === $this->variables; + } + + public function serialize() + { + return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables)); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $unserialized = unserialize($serialized, array('allowed_classes' => false)); + } else { + $unserialized = unserialize($serialized); + } + + $this->prefix = $unserialized['prefix']; + $this->variables = $unserialized['variables']; + } + + private function findVariables() + { + $variables = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + $variables[$key] = $value; + } + } + + ksort($variables); + + return $variables; + } +} diff --git a/vendor/symfony/http-kernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Config/FileLocator.php new file mode 100644 index 0000000000..169c9ad6e5 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/FileLocator.php @@ -0,0 +1,56 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FileLocator extends BaseFileLocator +{ + private $kernel; + private $path; + + /** + * Constructor. + * + * @param KernelInterface $kernel A KernelInterface instance + * @param null|string $path The path the global resource directory + * @param array $paths An array of paths where to look for resources + */ + public function __construct(KernelInterface $kernel, $path = null, array $paths = array()) + { + $this->kernel = $kernel; + if (null !== $path) { + $this->path = $path; + $paths[] = $path; + } + + parent::__construct($paths); + } + + /** + * {@inheritdoc} + */ + public function locate($file, $currentPath = null, $first = true) + { + if (isset($file[0]) && '@' === $file[0]) { + return $this->kernel->locateResource($file, $this->path, $first); + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php new file mode 100644 index 0000000000..2c17125c5a --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; + +/** + * Responsible for resolving the arguments passed to an action. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class ArgumentResolver implements ArgumentResolverInterface +{ + private $argumentMetadataFactory; + + /** + * @var iterable|ArgumentValueResolverInterface[] + */ + private $argumentValueResolvers; + + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = array()) + { + $this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory(); + $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $arguments = array(); + + foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) { + foreach ($this->argumentValueResolvers as $resolver) { + if (!$resolver->supports($request, $metadata)) { + continue; + } + + $resolved = $resolver->resolve($request, $metadata); + + if (!$resolved instanceof \Generator) { + throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver))); + } + + foreach ($resolved as $append) { + $arguments[] = $append; + } + + // continue to the next controller argument + continue 2; + } + + $representative = $controller; + + if (is_array($representative)) { + $representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]); + } elseif (is_object($representative)) { + $representative = get_class($representative); + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + } + + return $arguments; + } + + public static function getDefaultArgumentValueResolvers() + { + return array( + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new SessionValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php new file mode 100644 index 0000000000..e58fd3ab2b --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the default value defined in the action signature when no value has been given. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class DefaultValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php new file mode 100644 index 0000000000..05be372d84 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a non-variadic argument's value from the request attributes. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class RequestAttributeValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return !$argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->attributes->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php new file mode 100644 index 0000000000..2a5060a612 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the same instance as the request object passed along. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class RequestValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php new file mode 100644 index 0000000000..b1da6a9a75 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a service keyed by _controller and argument name. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +final class ServiceValueResolver implements ArgumentValueResolverInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php new file mode 100644 index 0000000000..9e656d281b --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the Session. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class SessionValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return false; + } + + return $request->getSession() instanceof $type; + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->getSession(); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php new file mode 100644 index 0000000000..56ae5f191c --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a variadic argument's values from the request attributes. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class VariadicValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + $values = $request->attributes->get($argument->getName()); + + if (!is_array($values)) { + throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), gettype($values))); + } + + foreach ($values as $value) { + yield $value; + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php new file mode 100644 index 0000000000..5c51230966 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -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\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * An ArgumentResolverInterface instance knows how to determine the + * arguments for a specific action. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface ArgumentResolverInterface +{ + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request + * @param callable $controller + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When no value could be provided for a required argument + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php new file mode 100644 index 0000000000..fd7b09ecf2 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -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\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +interface ArgumentValueResolverInterface +{ + /** + * Whether this resolver can resolve the value for the given ArgumentMetadata. + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return bool + */ + public function supports(Request $request, ArgumentMetadata $argument); + + /** + * Returns the possible value(s). + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return \Generator + */ + public function resolve(Request $request, ArgumentMetadata $argument); +} diff --git a/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php new file mode 100644 index 0000000000..1a107c62f6 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +class ContainerControllerResolver extends ControllerResolver +{ + protected $container; + + public function __construct(ContainerInterface $container, LoggerInterface $logger = null) + { + $this->container = $container; + + parent::__construct($logger); + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return mixed A PHP callable + * + * @throws \LogicException When the name could not be parsed + * @throws \InvalidArgumentException When the controller class does not exist + */ + protected function createController($controller) + { + if (false !== strpos($controller, '::')) { + return parent::createController($controller); + } + + if (1 == substr_count($controller, ':')) { + // controller in the "service:method" notation + list($service, $method) = explode(':', $controller, 2); + + return array($this->container->get($service), $method); + } + + if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) { + // invokable controller in the "service" notation + return $service; + } + + throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller)); + } + + /** + * {@inheritdoc} + */ + protected function instantiateController($class) + { + if ($this->container->has($class)) { + return $this->container->get($class); + } + + return parent::instantiateController($class); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Controller/ControllerReference.php new file mode 100644 index 0000000000..3d1592e83a --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerReference.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @see FragmentRendererInterface + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + /** + * Constructor. + * + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Controller/ControllerResolver.php new file mode 100644 index 0000000000..f51a5a8efb --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,265 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver. + * + * This implementation uses the '_controller' request attribute to determine + * the controller to execute and uses the request attributes to determine + * the controller method arguments. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface +{ + private $logger; + + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If scalar types exists. + * + * @var bool + */ + private $supportsScalarTypes; + + /** + * Constructor. + * + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + * + * This method looks for a '_controller' request attribute that represents + * the controller name (a string like ClassName::MethodName). + */ + public function getController(Request $request) + { + if (!$controller = $request->attributes->get('_controller')) { + if (null !== $this->logger) { + $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); + } + + return false; + } + + if (is_array($controller)) { + return $controller; + } + + if (is_object($controller)) { + if (method_exists($controller, '__invoke')) { + return $controller; + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo())); + } + + if (false === strpos($controller, ':')) { + if (method_exists($controller, '__invoke')) { + return $this->instantiateController($controller); + } elseif (function_exists($controller)) { + return $controller; + } + } + + $callable = $this->createController($controller); + + if (!is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable))); + } + + return $callable; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + if (is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $r = new \ReflectionObject($controller); + $r = $r->getMethod('__invoke'); + } else { + $r = new \ReflectionFunction($controller); + } + + return $this->doGetArguments($request, $controller, $r->getParameters()); + } + + /** + * @param Request $request + * @param callable $controller + * @param \ReflectionParameter[] $parameters + * + * @return array The arguments to use when calling the action + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + protected function doGetArguments(Request $request, $controller, array $parameters) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) { + $arguments = array_merge($arguments, array_values($attributes[$param->name])); + } else { + $arguments[] = $attributes[$param->name]; + } + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) { + $arguments[] = null; + } else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } elseif (is_object($controller)) { + $repr = get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return callable A PHP callable + * + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (false === strpos($controller, '::')) { + throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return array($this->instantiateController($class), $method); + } + + /** + * Returns an instantiated controller. + * + * @param string $class A class name + * + * @return object + */ + protected function instantiateController($class) + { + return new $class(); + } + + private function getControllerError($callable) + { + if (is_string($callable)) { + if (false !== strpos($callable, '::')) { + $callable = explode('::', $callable); + } + + if (class_exists($callable) && !method_exists($callable, '__invoke')) { + return sprintf('Class "%s" does not have a method "__invoke".', $callable); + } + + if (!function_exists($callable)) { + return sprintf('Function "%s" does not exist.', $callable); + } + } + + if (!is_array($callable)) { + return sprintf('Invalid type for controller given, expected string or array, got "%s".', gettype($callable)); + } + + if (2 !== count($callable)) { + return sprintf('Invalid format for controller, expected array(controller, method) or controller::method.'); + } + + list($controller, $method) = $callable; + + if (is_string($controller) && !class_exists($controller)) { + return sprintf('Class "%s" does not exist.', $controller); + } + + $className = is_object($controller) ? get_class($controller) : $controller; + + if (method_exists($controller, $method)) { + return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); + } + + $collection = get_class_methods($controller); + + $alternatives = array(); + + foreach ($collection as $item) { + $lev = levenshtein($method, $item); + + if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) { + $alternatives[] = $item; + } + } + + asort($alternatives); + + $message = sprintf('Expected method "%s" on class "%s"', $method, $className); + + if (count($alternatives) > 0) { + $message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives)); + } else { + $message .= sprintf('. Available methods: "%s".', implode('", "', $collection)); + } + + return $message; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php new file mode 100644 index 0000000000..0dd7cce96d --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -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\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * It can also determine the arguments to pass to the Controller. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load + * controller but cannot because of some errors made by the developer. + * + * @param Request $request A Request instance + * + * @return callable|false A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \LogicException If the controller can't be found + */ + public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead. + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php new file mode 100644 index 0000000000..6fb0fa66ac --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -0,0 +1,44 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class TraceableArgumentResolver implements ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + + public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 0000000000..ce291b1e3e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,78 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * TraceableControllerResolver. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + private $argumentResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param ArgumentResolverInterface $argumentResolver Only required for BC + */ + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + $this->argumentResolver = $argumentResolver; + + // BC + if (null === $this->argumentResolver) { + $this->argumentResolver = $resolver; + } + + if (!$this->argumentResolver instanceof TraceableArgumentResolver) { + $this->argumentResolver = new TraceableArgumentResolver($this->argumentResolver, $this->stopwatch); + } + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('The %s method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), E_USER_DEPRECATED); + + $ret = $this->argumentResolver->getArguments($request, $controller); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php new file mode 100644 index 0000000000..32316a8d51 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Responsible for storing metadata of an argument. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +class ArgumentMetadata +{ + private $name; + private $type; + private $isVariadic; + private $hasDefaultValue; + private $defaultValue; + private $isNullable; + + /** + * @param string $name + * @param string $type + * @param bool $isVariadic + * @param bool $hasDefaultValue + * @param mixed $defaultValue + * @param bool $isNullable + */ + public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false) + { + $this->name = $name; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->hasDefaultValue = $hasDefaultValue; + $this->defaultValue = $defaultValue; + $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); + } + + /** + * Returns the name as given in PHP, $foo would yield "foo". + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the type of the argument. + * + * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Returns whether the argument is defined as "...$variadic". + * + * @return bool + */ + public function isVariadic() + { + return $this->isVariadic; + } + + /** + * Returns whether the argument has a default value. + * + * Implies whether an argument is optional. + * + * @return bool + */ + public function hasDefaultValue() + { + return $this->hasDefaultValue; + } + + /** + * Returns whether the argument accepts null values. + * + * @return bool + */ + public function isNullable() + { + return $this->isNullable; + } + + /** + * Returns the default value of the argument. + * + * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} + * + * @return mixed + */ + public function getDefaultValue() + { + if (!$this->hasDefaultValue) { + throw new \LogicException(sprintf('Argument $%s does not have a default value. Use %s::hasDefaultValue() to avoid this exception.', $this->name, __CLASS__)); + } + + return $this->defaultValue; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php new file mode 100644 index 0000000000..d1e7af2068 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -0,0 +1,129 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds {@see ArgumentMetadata} objects based on the given Controller. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface +{ + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If the reflection supports the getType() method to resolve types. + * + * Requires at least PHP 7.0.0 or HHVM 3.11.0 + * + * @var bool + */ + private $supportsParameterType; + + public function __construct() + { + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsParameterType = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + */ + public function createArgumentMetadata($controller) + { + $arguments = array(); + + if (is_array($controller)) { + $reflection = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $reflection = (new \ReflectionObject($controller))->getMethod('__invoke'); + } else { + $reflection = new \ReflectionFunction($controller); + } + + foreach ($reflection->getParameters() as $param) { + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull()); + } + + return $arguments; + } + + /** + * Returns whether an argument is variadic. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function isVariadic(\ReflectionParameter $parameter) + { + return $this->supportsVariadic && $parameter->isVariadic(); + } + + /** + * Determines whether an argument has a default value. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function hasDefaultValue(\ReflectionParameter $parameter) + { + return $parameter->isDefaultValueAvailable(); + } + + /** + * Returns a default value if available. + * + * @param \ReflectionParameter $parameter + * + * @return mixed|null + */ + private function getDefaultValue(\ReflectionParameter $parameter) + { + return $this->hasDefaultValue($parameter) ? $parameter->getDefaultValue() : null; + } + + /** + * Returns an associated type to the given parameter if available. + * + * @param \ReflectionParameter $parameter + * + * @return null|string + */ + private function getType(\ReflectionParameter $parameter) + { + if ($this->supportsParameterType) { + if (!$type = $parameter->getType()) { + return; + } + $typeName = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString(); + if ('array' === $typeName && !$type->isBuiltin()) { + // Special case for HHVM with variadics + return; + } + + return $typeName; + } + + if (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $info)) { + return $info[1]; + } + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php new file mode 100644 index 0000000000..6ea179d783 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds method argument data. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +interface ArgumentMetadataFactoryInterface +{ + /** + * @param mixed $controller The controller to resolve the arguments for + * + * @return ArgumentMetadata[] + */ + public function createArgumentMetadata($controller); +} diff --git a/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php new file mode 100644 index 0000000000..b8405d5945 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * AjaxDataCollector. + * + * @author Bart van den Burg <bart@burgov.nl> + */ +class AjaxDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // all collecting is done client side + } + + public function getName() + { + return 'ajax'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 0000000000..87c4c08e69 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,330 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Caster\LinkStub; + +/** + * ConfigDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var KernelInterface + */ + private $kernel; + private $name; + private $version; + private $hasVarDumper; + + /** + * Constructor. + * + * @param string $name The name of the application using the web profiler + * @param string $version The version of the application using the web profiler + */ + public function __construct($name = null, $version = null) + { + $this->name = $name; + $this->version = $version; + $this->hasVarDumper = class_exists(LinkStub::class); + } + + /** + * Sets the Kernel associated with this Request. + * + * @param KernelInterface $kernel A KernelInterface instance + */ + public function setKernel(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'app_name' => $this->name, + 'app_version' => $this->version, + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'symfony_state' => 'unknown', + 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => PHP_VERSION, + 'php_architecture' => PHP_INT_SIZE * 8, + 'php_intl_locale' => class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', + 'php_timezone' => date_default_timezone_get(), + 'xdebug_enabled' => extension_loaded('xdebug'), + 'apcu_enabled' => extension_loaded('apcu') && ini_get('apc.enabled'), + 'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'), + 'bundles' => array(), + 'sapi_name' => PHP_SAPI, + ); + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath(); + } + + $this->data['symfony_state'] = $this->determineSymfonyState(); + $this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE); + $this->data['symfony_eom'] = $eom->format('F Y'); + $this->data['symfony_eol'] = $eol->format('F Y'); + } + + if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { + $this->data['php_version'] = $matches[1]; + $this->data['php_version_extra'] = $matches[2]; + } + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getApplicationName() + { + return $this->data['app_name']; + } + + public function getApplicationVersion() + { + return $this->data['app_version']; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + * + * @return string The Symfony version + */ + public function getSymfonyVersion() + { + return $this->data['symfony_version']; + } + + /** + * Returns the state of the current Symfony release. + * + * @return string One of: unknown, dev, stable, eom, eol + */ + public function getSymfonyState() + { + return $this->data['symfony_state']; + } + + /** + * Returns the minor Symfony version used (without patch numbers of extra + * suffix like "RC", "beta", etc.). + * + * @return string + */ + public function getSymfonyMinorVersion() + { + return $this->data['symfony_minor_version']; + } + + /** + * Returns the human redable date when this Symfony version ends its + * maintenance period. + * + * @return string + */ + public function getSymfonyEom() + { + return $this->data['symfony_eom']; + } + + /** + * Returns the human redable date when this Symfony version reaches its + * "end of life" and won't receive bugs or security fixes. + * + * @return string + */ + public function getSymfonyEol() + { + return $this->data['symfony_eol']; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * Gets the PHP version extra part. + * + * @return string|null The extra part + */ + public function getPhpVersionExtra() + { + return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null; + } + + /** + * @return int The PHP architecture as number of bits (e.g. 32 or 64) + */ + public function getPhpArchitecture() + { + return $this->data['php_architecture']; + } + + /** + * @return string + */ + public function getPhpIntlLocale() + { + return $this->data['php_intl_locale']; + } + + /** + * @return string + */ + public function getPhpTimezone() + { + return $this->data['php_timezone']; + } + + /** + * Gets the application name. + * + * @return string The application name + */ + public function getAppName() + { + return $this->data['name']; + } + + /** + * Gets the environment. + * + * @return string The environment + */ + public function getEnv() + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return bool true if debug is enabled, false otherwise + */ + public function isDebug() + { + return $this->data['debug']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return bool true if XDebug is enabled, false otherwise + */ + public function hasXDebug() + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if APCu is enabled. + * + * @return bool true if APCu is enabled, false otherwise + */ + public function hasApcu() + { + return $this->data['apcu_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + * + * @return bool true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() + { + return $this->data['zend_opcache_enabled']; + } + + public function getBundles() + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() + { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'config'; + } + + /** + * Tries to retrieve information about the current Symfony version. + * + * @return string One of: dev, stable, eom, eol + */ + private function determineSymfonyState() + { + $now = new \DateTime(); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE)->modify('last day of this month'); + + if ($now > $eol) { + $versionState = 'eol'; + } elseif ($now > $eom) { + $versionState = 'eom'; + } elseif ('' !== Kernel::EXTRA_VERSION) { + $versionState = 'dev'; + } else { + $versionState = 'stable'; + } + + return $versionState; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/DataCollector/DataCollector.php new file mode 100644 index 0000000000..845c2c1380 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.php @@ -0,0 +1,128 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; +use Symfony\Component\VarDumper\Caster\CutStub; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Bernhard Schussek <bschussek@symfony.com> + */ +abstract class DataCollector implements DataCollectorInterface, \Serializable +{ + protected $data = array(); + + /** + * @var ValueExporter + */ + private $valueExporter; + + /** + * @var ClonerInterface + */ + private $cloner; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts the variable into a serializable Data instance. + * + * This array can be displayed in the template using + * the VarDumper component. + * + * @param mixed $var + * + * @return Data + */ + protected function cloneVar($var) + { + if ($var instanceof Data) { + return $var; + } + if (null === $this->cloner) { + if (class_exists(CutStub::class)) { + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(-1); + $this->cloner->addCasters($this->getCasters()); + } else { + @trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED); + $this->cloner = false; + } + } + if (false === $this->cloner) { + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } + + return $this->cloner->cloneVar($var); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + * + * @deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead. + */ + protected function varToString($var) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } + + /** + * @return callable[] The casters to add to the cloner + */ + protected function getCasters() + { + return array( + '*' => function ($v, array $a, Stub $s, $isNested) { + if (!$v instanceof Stub) { + foreach ($a as $k => $v) { + if (is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) { + $a[$k] = new CutStub($v); + } + } + } + + return $a; + }, + ); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 0000000000..2820ad5b28 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface DataCollectorInterface +{ + /** + * Collects data for the given Request and Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An Exception instance + */ + public function collect(Request $request, Response $response, \Exception $exception = null); + + /** + * Returns the name of the collector. + * + * @return string The collector name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php new file mode 100644 index 0000000000..b50386cb96 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,303 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Twig\Template; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private $stopwatch; + private $fileLinkFormat; + private $dataCount = 0; + private $isCollected = true; + private $clonesCount = 0; + private $clonesIndex = 0; + private $rootRefs; + private $charset; + private $requestStack; + private $dumper; + private $dumperIsInjected; + + public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) + { + $this->stopwatch = $stopwatch; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; + $this->requestStack = $requestStack; + $this->dumper = $dumper; + $this->dumperIsInjected = null !== $dumper; + + // All clones share these properties by reference: + $this->rootRefs = array( + &$this->data, + &$this->dataCount, + &$this->isCollected, + &$this->clonesCount, + ); + } + + public function __clone() + { + $this->clonesIndex = ++$this->clonesCount; + } + + public function dump(Data $data) + { + if ($this->stopwatch) { + $this->stopwatch->start('dump'); + } + if ($this->isCollected) { + $this->isCollected = false; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 7); + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 1; $i < 7; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 7) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = array(); + + for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>'; + } + + $fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>'; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + if ($this->dumper) { + $this->doDump($data, $name, $file, $line); + } + + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + ++$this->dataCount; + + if ($this->stopwatch) { + $this->stopwatch->stop('dump'); + } + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // Sub-requests and programmatic calls stay in the collected profile. + if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + return; + } + + // In all other conditions that remove the web debug toolbar, dumps are written on the output. + if (!$this->requestStack + || !$response->headers->has('X-Debug-Token') + || $response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $request->getRequestFormat() + || false === strripos($response->getContent(), '</body>') + ) { + if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $dump) { + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + } + } + + public function serialize() + { + if ($this->clonesCount !== $this->clonesIndex) { + return 'a:0:{}'; + } + + $this->data[] = $this->fileLinkFormat; + $this->data[] = $this->charset; + $ser = serialize($this->data); + $this->data = array(); + $this->dataCount = 0; + $this->isCollected = true; + if (!$this->dumperIsInjected) { + $this->dumper = null; + } + + return $ser; + } + + public function unserialize($data) + { + parent::unserialize($data); + $charset = array_pop($this->data); + $fileLinkFormat = array_pop($this->data); + $this->dataCount = count($this->data); + self::__construct($this->stopwatch, $fileLinkFormat, $charset); + } + + public function getDumpsCount() + { + return $this->dataCount; + } + + public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) + { + $data = fopen('php://memory', 'r+b'); + + if ('html' === $format) { + $dumper = new HtmlDumper($data, $this->charset); + $dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); + } + $dumps = array(); + + foreach ($this->data as $dump) { + $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); + $dump['data'] = stream_get_contents($data, -1, 0); + ftruncate($data, 0); + rewind($data); + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName() + { + return 'dump'; + } + + public function __destruct() + { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { + $this->clonesCount = 0; + $this->isCollected = true; + + $h = headers_list(); + $i = count($h); + array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $i => $dump) { + $this->data[$i] = null; + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + + $this->data = array(); + $this->dataCount = 0; + } + } + + private function doDump($data, $name, $file, $line) + { + if ($this->dumper instanceof CliDumper) { + $contextDumper = function ($name, $file, $line, $fmt) { + if ($this instanceof HtmlDumper) { + if ($file) { + $s = $this->style('meta', '%s'); + $f = strip_tags($this->style('', $file)); + $name = strip_tags($this->style('', $name)); + if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $file, '%l' => $line)) : $fmt->format($file, $line)) { + $name = sprintf('<a href="%s" title="%s">'.$s.'</a>', strip_tags($this->style('', $link)), $f, $name); + } else { + $name = sprintf('<abbr title="%s">'.$s.'</abbr>', $f, $name); + } + } else { + $name = $this->style('meta', $name); + } + $this->line = $name.' on line '.$this->style('meta', $line).':'; + } else { + $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + } + $this->dumpLine(0); + }; + $contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); + $contextDumper($name, $file, $line, $this->fileLinkFormat); + } else { + $cloner = new VarCloner(); + $this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + } + $this->dumper->dump($data); + } + + private function htmlEncode($s) + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php new file mode 100644 index 0000000000..3d75f322d8 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -0,0 +1,108 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; + +/** + * EventDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class EventDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher = null) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'called_listeners' => array(), + 'not_called_listeners' => array(), + ); + } + + public function lateCollect() + { + if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { + $this->setCalledListeners($this->dispatcher->getCalledListeners()); + $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); + } + $this->data = $this->cloneVar($this->data); + } + + /** + * 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'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 0000000000..9fe826446b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,104 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * ExceptionDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExceptionDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $exception) { + $this->data = array( + 'exception' => FlattenException::create($exception), + ); + } + } + + /** + * Checks if the exception is not null. + * + * @return bool true if the exception is not null, false otherwise + */ + public function hasException() + { + return isset($this->data['exception']); + } + + /** + * Gets the exception. + * + * @return \Exception The exception + */ + public function getException() + { + return $this->data['exception']; + } + + /** + * Gets the exception message. + * + * @return string The exception message + */ + public function getMessage() + { + return $this->data['exception']->getMessage(); + } + + /** + * Gets the exception code. + * + * @return int The exception code + */ + public function getCode() + { + return $this->data['exception']->getCode(); + } + + /** + * Gets the status code. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->data['exception']->getStatusCode(); + } + + /** + * Gets the exception trace. + * + * @return array The exception trace + */ + public function getTrace() + { + return $this->data['exception']->getTrace(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php new file mode 100644 index 0000000000..012332de47 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * LateDataCollectorInterface. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface LateDataCollectorInterface +{ + /** + * Collects data as late as possible. + */ + public function lateCollect(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 0000000000..92fd0da71e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,262 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $logger; + private $containerPathPrefix; + + public function __construct($logger = null, $containerPathPrefix = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + + $this->containerPathPrefix = $containerPathPrefix; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // everything is done as late as possible + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->logger) { + $containerDeprecationLogs = $this->getContainerDeprecationLogs(); + $this->data = $this->computeErrorsCount($containerDeprecationLogs); + $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); + $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); + $this->data = $this->cloneVar($this->data); + } + } + + /** + * Gets the logs. + * + * @return array An array of logs + */ + public function getLogs() + { + return isset($this->data['logs']) ? $this->data['logs'] : array(); + } + + public function getPriorities() + { + return isset($this->data['priorities']) ? $this->data['priorities'] : array(); + } + + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + public function countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + public function countWarnings() + { + return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; + } + + public function countScreams() + { + return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + } + + public function getCompilerLogs() + { + return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : array(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function getContainerDeprecationLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { + return array(); + } + + $bootTime = filemtime($file); + $logs = array(); + foreach (unserialize(file_get_contents($file)) as $log) { + $log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])); + $log['timestamp'] = $bootTime; + $log['priority'] = 100; + $log['priorityName'] = 'DEBUG'; + $log['channel'] = '-'; + $log['scream'] = false; + unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); + $logs[] = $log; + } + + return $logs; + } + + private function getContainerCompilerLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { + return array(); + } + + $logs = array(); + foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) { + $log = explode(': ', $log, 2); + if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { + $log = array('Unknown Compiler Pass', implode(': ', $log)); + } + + $logs[$log[0]][] = array('message' => $log[1]); + } + + return $logs; + } + + private function sanitizeLogs($logs) + { + $sanitizedLogs = array(); + + foreach ($logs as $log) { + if (!$this->isSilencedOrDeprecationErrorLog($log)) { + $sanitizedLogs[] = $log; + + continue; + } + + $message = $log['message']; + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + + if (!isset($sanitizedLogs[$message])) { + $sanitizedLogs[$message] = $log + array( + 'errorCount' => 0, + 'scream' => true, + ); + } + $sanitizedLogs[$message]['errorCount'] += $exception->count; + + continue; + } + + $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); + + if (isset($sanitizedLogs[$errorId])) { + ++$sanitizedLogs[$errorId]['errorCount']; + } else { + $log += array( + 'errorCount' => 1, + 'scream' => false, + ); + + $sanitizedLogs[$errorId] = $log; + } + } + + return array_values($sanitizedLogs); + } + + private function isSilencedOrDeprecationErrorLog(array $log) + { + if (!isset($log['context']['exception'])) { + return false; + } + + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + return true; + } + + if ($exception instanceof \ErrorException && in_array($exception->getSeverity(), array(E_DEPRECATED, E_USER_DEPRECATED), true)) { + return true; + } + + return false; + } + + private function computeErrorsCount(array $containerDeprecationLogs) + { + $silencedLogs = array(); + $count = array( + 'error_count' => $this->logger->countErrors(), + 'deprecation_count' => 0, + 'warning_count' => 0, + 'scream_count' => 0, + 'priorities' => array(), + ); + + foreach ($this->logger->getLogs() as $log) { + if (isset($count['priorities'][$log['priority']])) { + ++$count['priorities'][$log['priority']]['count']; + } else { + $count['priorities'][$log['priority']] = array( + 'count' => 1, + 'name' => $log['priorityName'], + ); + } + if ('WARNING' === $log['priorityName']) { + ++$count['warning_count']; + } + + if ($this->isSilencedOrDeprecationErrorLog($log)) { + $exception = $log['context']['exception']; + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + $count['scream_count'] += $exception->count; + } else { + ++$count['deprecation_count']; + } + } + } + + foreach ($containerDeprecationLogs as $deprecationLog) { + $count['deprecation_count'] += $deprecationLog['context']['exception']->count; + } + + ksort($count['priorities']); + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 0000000000..9385010844 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,109 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * MemoryDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct() + { + $this->data = array( + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->updateMemoryUsage(); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return int The memory + */ + public function getMemory() + { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return int The memory limit + */ + public function getMemoryLimit() + { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'memory'; + } + + private function convertToBytes($memoryLimit) + { + if ('-1' === $memoryLimit) { + return -1; + } + + $memoryLimit = strtolower($memoryLimit); + $max = strtolower(ltrim($memoryLimit, '+')); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($memoryLimit, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php new file mode 100644 index 0000000000..7049844f9e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,397 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +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, LateDataCollectorInterface +{ + /** @var \SplObjectStorage */ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = array(); + $route = ''; + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key) { + $route = is_object($value) ? $value->getPath() : $value; + $attributes[$key] = $route; + } else { + $attributes[$key] = $value; + } + } + + $content = null; + try { + $content = $request->getContent(); + } catch (\LogicException $e) { + // the user already got the request content as a resource + $content = false; + } + + $sessionMetadata = array(); + $sessionAttributes = array(); + $session = null; + $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(); + + $responseCookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $responseCookies[$cookie->getName()] = $cookie; + } + + $this->data = array( + 'method' => $request->getMethod(), + 'format' => $request->getRequestFormat(), + 'content' => $content, + 'content_type' => $response->headers->get('Content-Type', 'text/html'), + 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_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, + 'route' => $route, + 'response_headers' => $response->headers->all(), + 'response_cookies' => $responseCookies, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + ); + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->data['request_request']['_password'])) { + $this->data['request_request']['_password'] = '******'; + } + + foreach ($this->data as $key => $value) { + if (!is_array($value)) { + continue; + } + if ('request_headers' === $key || 'response_headers' === $key) { + $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + } + } + + if (isset($this->controllers[$request])) { + $this->data['controller'] = $this->parseController($this->controllers[$request]); + unset($this->controllers[$request]); + } + + if (null !== $session && $session->isStarted()) { + if ($request->attributes->has('_redirected')) { + $this->data['redirect'] = $session->remove('sf_redirect'); + } + + if ($response->isRedirect()) { + $session->set('sf_redirect', array( + 'token' => $response->headers->get('x-debug-token'), + 'route' => $request->attributes->get('_route', 'n/a'), + 'method' => $request->getMethod(), + 'controller' => $this->parseController($request->attributes->get('_controller')), + 'status_code' => $statusCode, + 'status_text' => Response::$statusTexts[(int) $statusCode], + )); + } + } + + $this->data['identifier'] = $this->data['route'] ?: (is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']); + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getMethod() + { + return $this->data['method']; + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']->getValue()); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']->getValue()); + } + + public function getRequestHeaders() + { + return new ParameterBag($this->data['request_headers']->getValue()); + } + + public function getRequestServer($raw = false) + { + return new ParameterBag($this->data['request_server']->getValue($raw)); + } + + public function getRequestCookies($raw = false) + { + return new ParameterBag($this->data['request_cookies']->getValue($raw)); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']->getValue()); + } + + public function getResponseHeaders() + { + return new ParameterBag($this->data['response_headers']->getValue()); + } + + public function getResponseCookies() + { + return new ParameterBag($this->data['response_cookies']->getValue()); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']->getValue(); + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']->getValue(); + } + + public function getFlashes() + { + return $this->data['flashes']->getValue(); + } + + 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 $this->data['route']; + } + + public function getIdentifier() + { + return $this->data['identifier']; + } + + /** + * 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']->getValue() : array(); + } + + /** + * Gets the parsed controller. + * + * @return array|string The controller as a string or array of data + * with keys 'class', 'method', 'file' and 'line' + */ + public function getController() + { + return $this->data['controller']; + } + + /** + * Gets the previous request attributes. + * + * @return array|bool A legacy array of data from the previous redirection response + * or false otherwise + */ + public function getRedirect() + { + return isset($this->data['redirect']) ? $this->data['redirect'] : false; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest() || !$event->getRequest()->hasSession() || !$event->getRequest()->getSession()->isStarted()) { + return; + } + + if ($event->getRequest()->getSession()->has('sf_redirect')) { + $event->getRequest()->attributes->set('_redirected', true); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } + + /** + * Parse a controller. + * + * @param mixed $controller The controller to parse + * + * @return array|string An array of controller data or a simple string + */ + protected function parseController($controller) + { + if (is_string($controller) && false !== strpos($controller, '::')) { + $controller = explode('::', $controller); + } + + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } catch (\ReflectionException $e) { + if (is_callable($controller)) { + // using __call or __callStatic + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + if (is_object($controller)) { + $r = new \ReflectionClass($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + return is_string($controller) ? $controller : 'n/a'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php new file mode 100644 index 0000000000..76d9623461 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + +/** + * RouterDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class RouterDataCollector extends DataCollector +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + + $this->data = array( + 'redirect' => false, + 'url' => null, + 'route' => null, + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + protected function guessRoute(Request $request, $controller) + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + * + * @param FilterControllerEvent $event The filter controller event + */ + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return bool Whether this request will result in a redirect + */ + public function getRedirect() + { + return $this->data['redirect']; + } + + /** + * @return string|null The target URL + */ + public function getTargetUrl() + { + return $this->data['url']; + } + + /** + * @return string|null The target route + */ + public function getTargetRoute() + { + return $this->data['route']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php new file mode 100644 index 0000000000..2d39156e69 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,136 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * TimeDataCollector. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TimeDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $kernel; + protected $stopwatch; + + public function __construct(KernelInterface $kernel = null, $stopwatch = null) + { + $this->kernel = $kernel; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT'); + } + + $this->data = array( + 'token' => $response->headers->get('X-Debug-Token'), + 'start_time' => $startTime * 1000, + 'events' => array(), + ); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->stopwatch && isset($this->data['token'])) { + $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); + } + unset($this->data['token']); + } + + /** + * Sets the request events. + * + * @param array $events The request events + */ + public function setEvents(array $events) + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * Gets the request events. + * + * @return array The request events + */ + public function getEvents() + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + * + * @return float The elapsed time + */ + public function getDuration() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + * + * @return float The elapsed time + */ + public function getInitTime() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + /** + * Gets the request time. + * + * @return int The time + */ + public function getStartTime() + { + return $this->data['start_time']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php new file mode 100644 index 0000000000..f1e48311c0 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php @@ -0,0 +1,99 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector\Util; + +@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since version 3.2 and will be removed in 4.0. Use the VarDumper component instead.', E_USER_DEPRECATED); + +/** + * @author Bernhard Schussek <bschussek@gmail.com> + * + * @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead. + */ +class ValueExporter +{ + /** + * Converts a PHP value to a string. + * + * @param mixed $value The PHP value + * @param int $depth only for internal usage + * @param bool $deep only for internal usage + * + * @return string The string representation of the given value + */ + public function exportValue($value, $depth = 1, $deep = false) + { + if ($value instanceof \__PHP_Incomplete_Class) { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); + } + + if (is_object($value)) { + if ($value instanceof \DateTimeInterface) { + return sprintf('Object(%s) - %s', get_class($value), $value->format(\DateTime::ATOM)); + } + + return sprintf('Object(%s)', get_class($value)); + } + + if (is_array($value)) { + if (empty($value)) { + return '[]'; + } + + $indent = str_repeat(' ', $depth); + + $a = array(); + foreach ($value as $k => $v) { + if (is_array($v)) { + $deep = true; + } + $a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep)); + } + + if ($deep) { + return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); + } + + $s = sprintf('[%s]', implode(', ', $a)); + + if (80 > strlen($s)) { + return $s; + } + + return sprintf("[\n%s%s\n]", $indent, implode(sprintf(",\n%s", $indent), $a)); + } + + if (is_resource($value)) { + return sprintf('Resource(%s#%d)', get_resource_type($value), $value); + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php new file mode 100644 index 0000000000..c340d9b672 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php @@ -0,0 +1,88 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Formats debug file links. + * + * @author Jérémy Romey <jeremy@free-agent.fr> + */ +class FileLinkFormatter implements \Serializable +{ + private $fileLinkFormat; + private $requestStack; + private $baseDir; + private $urlFormat; + + public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $urlFormat = null) + { + $fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + if ($fileLinkFormat && !is_array($fileLinkFormat)) { + $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f); + $fileLinkFormat = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); + } + + $this->fileLinkFormat = $fileLinkFormat; + $this->requestStack = $requestStack; + $this->baseDir = $baseDir; + $this->urlFormat = $urlFormat; + } + + public function format($file, $line) + { + if ($fmt = $this->getFileLinkFormat()) { + for ($i = 1; isset($fmt[$i]); ++$i) { + if (0 === strpos($file, $k = $fmt[$i++])) { + $file = substr_replace($file, $fmt[$i], 0, strlen($k)); + break; + } + } + + return strtr($fmt[0], array('%f' => $file, '%l' => $line)); + } + + return false; + } + + public function serialize() + { + return serialize($this->getFileLinkFormat()); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $this->fileLinkFormat = unserialize($serialized, array('allowed_classes' => false)); + } else { + $this->fileLinkFormat = unserialize($serialized); + } + } + + private function getFileLinkFormat() + { + if ($this->fileLinkFormat) { + return $this->fileLinkFormat; + } + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMasterRequest(); + if ($request instanceof Request) { + return array( + $request->getSchemeAndHttpHost().$request->getBaseUrl().$this->urlFormat, + $this->baseDir.DIRECTORY_SEPARATOR, '', + ); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000000..fbc49dffc8 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,82 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\Event; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TraceableEventDispatcher extends BaseTraceableEventDispatcher +{ + /** + * {@inheritdoc} + */ + protected function preDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::REQUEST: + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // There is a very special case when using built-in AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($token); + } catch (\LogicException $e) { + } + break; + } + } + + /** + * {@inheritdoc} + */ + protected function postDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::CONTROLLER_ARGUMENTS: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + $this->stopwatch->stopSection($token); + break; + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } catch (\LogicException $e) { + } + break; + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php new file mode 100644 index 0000000000..9235ae4e96 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -0,0 +1,153 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\HttpKernel\Kernel; + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class AddAnnotatedClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + $annotatedClasses = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + if (\PHP_VERSION_ID < 70000) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + $annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile()); + } + } + + $existingClasses = $this->getClassesInComposerClassMaps(); + + if (\PHP_VERSION_ID < 70000) { + $classes = $container->getParameterBag()->resolveValue($classes); + $this->kernel->setClassCache($this->expandClasses($classes, $existingClasses)); + } + $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); + $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); + } + + /** + * Expands the given class patterns using a list of existing classes. + * + * @param array $patterns The class patterns to expand + * @param array $classes The existing classes to match against the patterns + * + * @return array A list of classes derivated from the patterns + */ + private function expandClasses(array $patterns, array $classes) + { + $expanded = array(); + + // Explicit classes declared in the patterns are returned directly + foreach ($patterns as $key => $pattern) { + if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) { + unset($patterns[$key]); + $expanded[] = ltrim($pattern, '\\'); + } + } + + // Match patterns with the classes list + $regexps = $this->patternsToRegexps($patterns); + + foreach ($classes as $class) { + $class = ltrim($class, '\\'); + + if ($this->matchAnyRegexps($class, $regexps)) { + $expanded[] = $class; + } + } + + return array_unique($expanded); + } + + private function getClassesInComposerClassMaps() + { + $classes = array(); + + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if (is_array($function) && $function[0] instanceof ClassLoader) { + $classes += array_filter($function[0]->getClassMap()); + } + } + + return array_keys($classes); + } + + private function patternsToRegexps($patterns) + { + $regexps = array(); + + foreach ($patterns as $pattern) { + // Escape user input + $regex = preg_quote(ltrim($pattern, '\\')); + + // Wildcards * and ** + $regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?')); + + // If this class does not end by a slash, anchor the end + if (substr($regex, -1) !== '\\') { + $regex .= '$'; + } + + $regexps[] = '{^\\\\'.$regex.'}'; + } + + return $regexps; + } + + private function matchAnyRegexps($class, $regexps) + { + $blacklisted = false !== strpos($class, 'Test'); + + foreach ($regexps as $regex) { + if ($blacklisted && false === strpos($regex, 'Test')) { + continue; + } + + if (preg_match($regex, '\\'.$class)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 0000000000..4ffa17511d --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since version 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class AddClassesToCachePass extends AddAnnotatedClassesToCachePass +{ +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 0000000000..c7eca3063c --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if + * + * a) you use the Config/Definition component for configuration, + * b) your configuration class is named "Configuration", and + * c) the configuration class resides in the DependencyInjection sub-folder. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +abstract class ConfigurableExtension extends Extension +{ + /** + * {@inheritdoc} + */ + final public function load(array $configs, ContainerBuilder $container) + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + * + * @param array $mergedConfig + * @param ContainerBuilder $container + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php new file mode 100644 index 0000000000..343e217b96 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Gathers and configures the argument value resolvers. + * + * @author Iltar van der Berg <kjarli@gmail.com> + */ +class ControllerArgumentValueResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $argumentResolverService; + private $argumentValueResolverTag; + + public function __construct($argumentResolverService = 'argument_resolver', $argumentValueResolverTag = 'controller.argument_value_resolver') + { + $this->argumentResolverService = $argumentResolverService; + $this->argumentValueResolverTag = $argumentValueResolverTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->argumentResolverService)) { + return; + } + + $container + ->getDefinition($this->argumentResolverService) + ->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container))) + ; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 0000000000..f6449f4e52 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.php @@ -0,0 +1,77 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\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(); + private $annotatedClasses = array(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function getClassesToCompile() + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + return $this->classes; + } + + /** + * Gets the annotated classes to cache. + * + * @return array An array of classes + */ + public function getAnnotatedClassesToCompile() + { + return $this->annotatedClasses; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of class patterns + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function addClassesToCompile(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->classes = array_merge($this->classes, $classes); + } + + /** + * Adds annotated classes to the class cache. + * + * @param array $annotatedClasses An array of class patterns + */ + public function addAnnotatedClassesToCompile(array $annotatedClasses) + { + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 0000000000..ac52c8732b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -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\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FragmentRendererPass implements CompilerPassInterface +{ + private $handlerService; + private $rendererTag; + + /** + * @param string $handlerService Service name of the fragment handler in the container + * @param string $rendererTag Tag name used for fragments + */ + public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer') + { + $this->handlerService = $handlerService; + $this->rendererTag = $rendererTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->handlerService)) { + return; + } + + $definition = $container->getDefinition($this->handlerService); + $renderers = array(); + foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($def->getClass()); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(FragmentRendererInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); + } + + foreach ($tags as $tag) { + $renderers[$tag['alias']] = new Reference($id); + } + } + + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 0000000000..d6f4dab141 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -0,0 +1,81 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; + +/** + * Lazily loads fragment renderers from the dependency injection container. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class LazyLoadingFragmentHandler extends FragmentHandler +{ + private $container; + /** + * @deprecated since version 3.3, to be removed in 4.0 + */ + private $rendererIds = array(); + private $initialized = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A container + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false) + { + $this->container = $container; + + parent::__construct($requestStack, array(), $debug); + } + + /** + * Adds a service as a fragment renderer. + * + * @param string $name The service name + * @param string $renderer The render service id + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + public function addRendererService($name, $renderer) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->rendererIds[$name] = $renderer; + } + + /** + * {@inheritdoc} + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + // BC 3.x, to be removed in 4.0 + if (isset($this->rendererIds[$renderer])) { + $this->addRenderer($this->container->get($this->rendererIds[$renderer])); + unset($this->rendererIds[$renderer]); + + return parent::render($uri, $renderer, $options); + } + + if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { + $this->addRenderer($this->container->get($renderer)); + $this->initialized[$renderer] = true; + } + + return parent::render($uri, $renderer, $options); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000000..dcd73828de --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -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); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php new file mode 100644 index 0000000000..222185f527 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -0,0 +1,158 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Creates the service-locators required by ServiceValueResolver. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + private $controllerTag; + + public function __construct($resolverServiceId = 'argument_resolver.service', $controllerTag = 'controller.service_arguments') + { + $this->resolverServiceId = $resolverServiceId; + $this->controllerTag = $controllerTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $parameterBag = $container->getParameterBag(); + $controllers = array(); + + foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $def->getClass(); + $autowire = $def->isAutowired(); + + // resolve service class, taking parent definitions into account + while (!$class && $def instanceof ChildDefinition) { + $def = $container->findDefinition($def->getParent()); + $class = $def->getClass(); + } + $class = $parameterBag->resolveValue($class); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + $isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class); + + // get regular public methods + $methods = array(); + $arguments = array(); + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { + if ('setContainer' === $r->name && $isContainerAware) { + continue; + } + if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { + $methods[strtolower($r->name)] = array($r, $r->getParameters()); + } + } + + // validate and collect explicit per-actions and per-arguments service references + foreach ($tags as $attributes) { + if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) { + $autowire = true; + continue; + } + foreach (array('action', 'argument', 'id') as $k) { + if (!isset($attributes[$k][0])) { + throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, json_encode($attributes, JSON_UNESCAPED_UNICODE), $id)); + } + } + if (!isset($methods[$action = strtolower($attributes['action'])])) { + throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); + } + list($r, $parameters) = $methods[$action]; + $found = false; + + foreach ($parameters as $p) { + if ($attributes['argument'] === $p->name) { + if (!isset($arguments[$r->name][$p->name])) { + $arguments[$r->name][$p->name] = $attributes['id']; + } + $found = true; + break; + } + } + + if (!$found) { + throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class)); + } + } + + foreach ($methods as list($r, $parameters)) { + /** @var \ReflectionMethod $r */ + + // create a per-method map of argument-names to service/type-references + $args = array(); + foreach ($parameters as $p) { + /** @var \ReflectionParameter $p */ + $type = $target = ProxyHelper::getTypeHint($r, $p, true); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + + if (isset($arguments[$r->name][$p->name])) { + $target = $arguments[$r->name][$p->name]; + if ('?' !== $target[0]) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('' === $target = (string) substr($target, 1)) { + throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); + } elseif ($p->allowsNull() && !$p->isOptional()) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + } elseif (!$type || !$autowire) { + continue; + } + + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { + $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); + + // see if the type-hint lives in the same namespace as the controller + if (0 === strncmp($type, $class, strrpos($class, '\\'))) { + $message .= ' Did you forget to add a use statement?'; + } + + throw new InvalidArgumentException($message); + } + + $args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior); + } + // register the maps as a per-method service-locators + if ($args) { + $controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args); + } + } + } + + $container->getDefinition($this->resolverServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php new file mode 100644 index 0000000000..440b0d0025 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes empty service-locators registered for ServiceValueResolver. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + + public function __construct($resolverServiceId = 'argument_resolver.service') + { + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $serviceResolver = $container->getDefinition($this->resolverServiceId); + $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllers = $controllerLocator->getArgument(0); + + foreach ($controllers as $controller => $argumentRef) { + $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); + + if (!$argumentLocator->getArgument(0)) { + // remove empty argument locators + $reason = sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); + } else { + // any methods listed for call-at-instantiation cannot be actions + $reason = false; + $action = substr(strrchr($controller, ':'), 1); + $id = substr($controller, 0, -1 - strlen($action)); + $controllerDef = $container->getDefinition($id); + foreach ($controllerDef->getMethodCalls() as list($method, $args)) { + if (0 === strcasecmp($action, $method)) { + $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); + break; + } + } + if (!$reason) { + if ($controllerDef->getClass() === $id) { + $controllers[$id.'::'.$action] = $argumentRef; + } + if ('__invoke' === $action) { + $controllers[$id] = $argumentRef; + } + continue; + } + } + + unset($controllers[$controller]); + $container->log($this, $reason); + } + + $controllerLocator->replaceArgument(0, $controllers); + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php new file mode 100644 index 0000000000..1dc784ed52 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php @@ -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\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of controller arguments. + * + * You can call getController() to retrieve the controller and getArguments + * to retrieve the current arguments. With setArguments() you can replace + * arguments that are used to call the controller. + * + * Arguments set in the event must be compatible with the signature of the + * controller. + * + * @author Christophe Coevoet <stof@notk.org> + */ +class FilterControllerArgumentsEvent extends FilterControllerEvent +{ + private $arguments; + + public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType) + { + parent::__construct($kernel, $controller, $request, $requestType); + + $this->arguments = $arguments; + } + + /** + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * @param array $arguments + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php new file mode 100644 index 0000000000..e0d46aa4ce --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php @@ -0,0 +1,61 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class FilterControllerEvent extends KernelEvent +{ + /** + * The current controller. + */ + private $controller; + + public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + /** + * Returns the current controller. + * + * @return callable + */ + public function getController() + { + return $this->controller; + } + + /** + * Sets a new controller. + * + * @param callable $controller + */ + public function setController(callable $controller) + { + $this->controller = $controller; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterResponseEvent.php b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php new file mode 100644 index 0000000000..ed816a9d32 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class FilterResponseEvent extends KernelEvent +{ + /** + * The current response object. + * + * @var Response + */ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + /** + * Returns the current response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a new response object. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Event/FinishRequestEvent.php b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php new file mode 100644 index 0000000000..ee724843cd --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +/** + * Triggered whenever a request is fully processed. + * + * @author Benjamin Eberlei <kontakt@beberlei.de> + */ +class FinishRequestEvent extends KernelEvent +{ +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseEvent.php b/vendor/symfony/http-kernel/Event/GetResponseEvent.php new file mode 100644 index 0000000000..4c96a4b7d9 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseEvent.php @@ -0,0 +1,65 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class GetResponseEvent extends KernelEvent +{ + /** + * The response object. + * + * @var Response + */ + private $response; + + /** + * Returns the response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + * + * @return bool Whether a response was set + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php new file mode 100644 index 0000000000..f70ce09e0a --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php @@ -0,0 +1,61 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class GetResponseForControllerResultEvent extends GetResponseEvent +{ + /** + * The return value of the controller. + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed The controller return value + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed $controllerResult The controller return value + */ + public function setControllerResult($controllerResult) + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php new file mode 100644 index 0000000000..751b74515b --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php @@ -0,0 +1,90 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setException() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class GetResponseForExceptionEvent extends GetResponseEvent +{ + /** + * The exception object. + * + * @var \Exception + */ + private $exception; + + /** + * @var bool + */ + private $allowCustomResponseCode = false; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setException($e); + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Mark the event as allowing a custom response code. + */ + public function allowCustomResponseCode() + { + $this->allowCustomResponseCode = true; + } + + /** + * Returns true if the event allows a custom response code. + * + * @return bool + */ + public function isAllowingCustomResponseCode() + { + return $this->allowCustomResponseCode; + } +} diff --git a/vendor/symfony/http-kernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Event/KernelEvent.php new file mode 100644 index 0000000000..2043a01758 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/KernelEvent.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\Event; + +/** + * Base class for events thrown in the HttpKernel component. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class KernelEvent extends Event +{ + /** + * The kernel in which this event was thrown. + * + * @var HttpKernelInterface + */ + private $kernel; + + /** + * The request the kernel is currently processing. + * + * @var Request + */ + private $request; + + /** + * The request type the kernel is currently processing. One of + * HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST. + * + * @var int + */ + private $requestType; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + { + $this->kernel = $kernel; + $this->request = $request; + $this->requestType = $requestType; + } + + /** + * Returns the kernel in which this event was thrown. + * + * @return HttpKernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing. + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing. + * + * @return int One of HttpKernelInterface::MASTER_REQUEST and + * HttpKernelInterface::SUB_REQUEST + */ + public function getRequestType() + { + return $this->requestType; + } + + /** + * Checks if this is a master request. + * + * @return bool True if the request is a master request + */ + public function isMasterRequest() + { + return HttpKernelInterface::MASTER_REQUEST === $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Event/PostResponseEvent.php b/vendor/symfony/http-kernel/Event/PostResponseEvent.php new file mode 100644 index 0000000000..2406fddbe8 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/PostResponseEvent.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on master requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MASTER_REQUEST`. + * + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +class PostResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $this->response = $response; + } + + /** + * Returns the response for which this event was thrown. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php new file mode 100644 index 0000000000..7a6c20734b --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -0,0 +1,54 @@ +<?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\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Sets the session in the request. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +abstract class AbstractSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $request = $event->getRequest(); + $session = $this->getSession(); + if (null === $session || $request->hasSession()) { + return; + } + + $request->setSession($session); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php new file mode 100644 index 0000000000..eb6e66c0eb --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php @@ -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\EventListener; + +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * TestSessionListener. + * + * Saves session in test environment. + * + * @author Bulat Shakirzyanov <mallluhuct@gmail.com> + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class AbstractTestSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + // bootstrap the session + $session = $this->getSession(); + if (!$session) { + return; + } + + $cookies = $event->getRequest()->cookies; + + if ($cookies->has($session->getName())) { + $session->setId($cookies->get($session->getName())); + } + } + + /** + * Checks if session was initialized and saves if current request is master + * Runs on 'kernel.response' in test environment. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + $params = session_get_cookie_params(); + $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 192), + KernelEvents::RESPONSE => array('onKernelResponse', -128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 0000000000..280844c183 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -0,0 +1,57 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +/** + * Adds configured formats to each request. + * + * @author Gildas Quemener <gildas.quemener@gmail.com> + */ +class AddRequestFormatsListener implements EventSubscriberInterface +{ + /** + * @var array + */ + protected $formats; + + /** + * @param array $formats + */ + public function __construct(array $formats) + { + $this->formats = $formats; + } + + /** + * Adds request formats. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + foreach ($this->formats as $format => $mimeTypes) { + $event->getRequest()->setFormat($format, $mimeTypes); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('onKernelRequest', 1)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 0000000000..29e1725adc --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,146 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * Configures errors and exceptions handlers. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private $exceptionHandler; + private $logger; + private $levels; + private $throwAt; + private $scream; + private $fileLinkFormat; + private $scope; + private $firstCall = true; + + /** + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string|array $fileLinkFormat The format for links to source files + * @param bool $scope Enables/disables scoping mode + */ + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) + { + $this->exceptionHandler = $exceptionHandler; + $this->logger = $logger; + $this->levels = null === $levels ? E_ALL : $levels; + $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? E_ALL : null)); + $this->scream = (bool) $scream; + $this->fileLinkFormat = $fileLinkFormat; + $this->scope = (bool) $scope; + } + + /** + * Configures the error handler. + * + * @param Event|null $event The triggering event + */ + public function configure(Event $event = null) + { + if (!$this->firstCall) { + return; + } + $this->firstCall = false; + if ($this->logger || null !== $this->throwAt) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler instanceof ErrorHandler) { + if ($this->logger) { + $handler->setDefaultLogger($this->logger, $this->levels); + if (is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($this->levels); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + } + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + if (method_exists($event->getKernel(), 'terminateWithException')) { + $this->exceptionHandler = array($event->getKernel(), 'terminateWithException'); + } + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = function ($e) use ($app, $output) { + $app->renderException($e, $output); + }; + } + } + if ($this->exceptionHandler) { + $handler = set_exception_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + if ($handler instanceof ErrorHandler) { + $h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler; + $handler->setExceptionHandler($h); + $handler = is_array($h) ? $h[0] : null; + } + if ($handler instanceof ExceptionHandler) { + $handler->setHandler($this->exceptionHandler); + if (null !== $this->fileLinkFormat) { + $handler->setFileLinkFormat($this->fileLinkFormat); + } + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents() + { + $events = array(KernelEvents::REQUEST => array('configure', 2048)); + + if ('cli' === PHP_SAPI && defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = array('configure', 2048); + } + + return $events; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DumpListener.php b/vendor/symfony/http-kernel/EventListener/DumpListener.php new file mode 100644 index 0000000000..88acef3187 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.php @@ -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\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class DumpListener implements EventSubscriberInterface +{ + private $cloner; + private $dumper; + + /** + * @param ClonerInterface $cloner Cloner service + * @param DataDumperInterface $dumper Dumper service + */ + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper) + { + $this->cloner = $cloner; + $this->dumper = $dumper; + } + + public function configure() + { + $cloner = $this->cloner; + $dumper = $this->dumper; + + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } + + public static function getSubscribedEvents() + { + if (!class_exists(ConsoleEvents::class)) { + return array(); + } + + // Register early to have a working dump() as early as possible + return array(ConsoleEvents::COMMAND => array('configure', 1024)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ExceptionListener.php b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php new file mode 100644 index 0000000000..cf3a2f0a53 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php @@ -0,0 +1,116 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ExceptionListener. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExceptionListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + + public function __construct($controller, LoggerInterface $logger = null) + { + $this->controller = $controller; + $this->logger = $logger; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $request = $event->getRequest(); + + $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); + + $request = $this->duplicateRequest($exception, $request); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine())); + + $wrapper = $e; + + while ($prev = $wrapper->getPrevious()) { + if ($exception === $wrapper = $prev) { + throw $e; + } + } + + $prev = new \ReflectionProperty('Exception', 'previous'); + $prev->setAccessible(true); + $prev->setValue($wrapper, $exception); + + throw $e; + } + + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -128), + ); + } + + /** + * Logs an exception. + * + * @param \Exception $exception The \Exception instance + * @param string $message The error message to log + */ + protected function logException(\Exception $exception, $message) + { + if (null !== $this->logger) { + if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { + $this->logger->critical($message, array('exception' => $exception)); + } else { + $this->logger->error($message, array('exception' => $exception)); + } + } + } + + /** + * Clones the request for the exception. + * + * @param \Exception $exception The thrown exception + * @param Request $request The original request + * + * @return Request $request The cloned request + */ + protected function duplicateRequest(\Exception $exception, Request $request) + { + $attributes = array( + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + ); + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/EventListener/FragmentListener.php new file mode 100644 index 0000000000..37bf15c3a0 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/FragmentListener.php @@ -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\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * If throws an AccessDeniedHttpException exception if the request + * is not signed or if it is not an internal sub-request. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FragmentListener implements EventSubscriberInterface +{ + private $signer; + private $fragmentPath; + + /** + * Constructor. + * + * @param UriSigner $signer A UriSigner instance + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + { + $this->signer = $signer; + $this->fragmentPath = $fragmentPath; + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @param GetResponseEvent $event A GetResponseEvent instance + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + if ($request->attributes->has('_controller')) { + // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. + $request->query->remove('_path'); + + return; + } + + if ($event->isMasterRequest()) { + $this->validateRequest($request); + } + + parse_str($request->query->get('_path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request) + { + // is the Request safe? + if (!$request->isMethodSafe(false)) { + throw new AccessDeniedHttpException(); + } + + // is the Request signed? + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + return; + } + + throw new AccessDeniedHttpException(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 48)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/EventListener/LocaleListener.php new file mode 100644 index 0000000000..99fc786793 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleListener.php @@ -0,0 +1,85 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class LocaleListener implements EventSubscriberInterface +{ + private $router; + private $defaultLocale; + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack A RequestStack instance + * @param string $defaultLocale The default locale + * @param RequestContextAwareInterface|null $router The router + */ + public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null) + { + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->router = $router; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->router) { + $this->router->getContext()->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php new file mode 100644 index 0000000000..c3772b688e --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php @@ -0,0 +1,135 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ProfilerListener collects data for the current request by listening to the kernel events. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ProfilerListener implements EventSubscriberInterface +{ + protected $profiler; + protected $matcher; + protected $onlyException; + protected $onlyMasterRequests; + protected $exception; + protected $profiles; + protected $requestStack; + protected $parents; + + /** + * Constructor. + * + * @param Profiler $profiler A Profiler instance + * @param RequestStack $requestStack A RequestStack instance + * @param RequestMatcherInterface|null $matcher A RequestMatcher instance + * @param bool $onlyException true if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise + */ + public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false) + { + $this->profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (bool) $onlyException; + $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requestStack = $requestStack; + } + + /** + * Handles the onKernelException event. + * + * @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + return; + } + + $this->exception = $event->getException(); + } + + /** + * Handles the onKernelResponse event. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $master = $event->isMasterRequest(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[$request] = $profile; + + $this->parents[$request] = $this->requestStack->getParentRequest(); + } + + public function onKernelTerminate(PostResponseEvent $event) + { + // attach children to parents + foreach ($this->profiles as $request) { + // isset call should be removed when requestStack is required + if (isset($this->parents[$request]) && null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // save profiles + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -100), + KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::TERMINATE => array('onKernelTerminate', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/EventListener/ResponseListener.php new file mode 100644 index 0000000000..eeb2b0fcb2 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ResponseListener.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ResponseListener implements EventSubscriberInterface +{ + private $charset; + + public function __construct($charset) + { + $this->charset = $charset; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/EventListener/RouterListener.php new file mode 100644 index 0000000000..3c46be8608 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.php @@ -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\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class RouterListener implements EventSubscriberInterface +{ + private $matcher; + private $context; + private $logger; + private $requestStack; + + /** + * Constructor. + * + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestStack $requestStack A RequestStack instance + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param LoggerInterface|null $logger The logger + * + * @throws \InvalidArgumentException + */ + public function __construct($matcher, RequestStack $requestStack, 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->requestStack = $requestStack; + $this->logger = $logger; + } + + private function setCurrentRequest(Request $request = null) + { + if (null !== $request) { + $this->context->fromRequest($request); + } + } + + /** + * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator + * operates on the correct context again. + * + * @param FinishRequestEvent $event + */ + public function onKernelFinishRequest(FinishRequestEvent $event) + { + $this->setCurrentRequest($this->requestStack->getParentRequest()); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + $this->setCurrentRequest($request); + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + if (null !== $this->logger) { + $this->logger->info('Matched route "{route}".', array( + 'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a', + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + 'method' => $request->getMethod(), + )); + } + + $request->attributes->add($parameters); + unset($parameters['_route'], $parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + + if ($referer = $request->headers->get('referer')) { + $message .= sprintf(' (from "%s")', $referer); + } + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods())); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php new file mode 100644 index 0000000000..36809b59af --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php @@ -0,0 +1,66 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + * + * @author Tobias Schultze <http://tobion.de> + */ +class SaveSessionListener implements EventSubscriberInterface +{ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + } + } + + public static function getSubscribedEvents() + { + return array( + // low priority but higher than StreamedResponseListener + KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SessionListener.php b/vendor/symfony/http-kernel/EventListener/SessionListener.php new file mode 100644 index 0000000000..39ebfd922f --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final since version 3.3 + */ +class SessionListener extends AbstractSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php new file mode 100644 index 0000000000..571cd74e34 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * StreamedResponseListener is responsible for sending the Response + * to the client. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class StreamedResponseListener implements EventSubscriberInterface +{ + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if ($response instanceof StreamedResponse) { + $response->send(); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SurrogateListener.php b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php new file mode 100644 index 0000000000..a207ab6731 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php @@ -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\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SurrogateListener implements EventSubscriberInterface +{ + private $surrogate; + + /** + * Constructor. + * + * @param SurrogateInterface $surrogate An SurrogateInterface instance + */ + public function __construct(SurrogateInterface $surrogate = null) + { + $this->surrogate = $surrogate; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $kernel = $event->getKernel(); + $surrogate = $this->surrogate; + if ($kernel instanceof HttpCache) { + $surrogate = $kernel->getSurrogate(); + if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) { + $surrogate = $this->surrogate; + } + } + + if (null === $surrogate) { + return; + } + + $surrogate->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TestSessionListener.php b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php new file mode 100644 index 0000000000..36abb422f4 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final since version 3.3 + */ +class TestSessionListener extends AbstractTestSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TranslatorListener.php b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php new file mode 100644 index 0000000000..6967ad0298 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php @@ -0,0 +1,69 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Synchronizes the locale between the request and the translator. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TranslatorListener implements EventSubscriberInterface +{ + private $translator; + private $requestStack; + + public function __construct(TranslatorInterface $translator, RequestStack $requestStack) + { + $this->translator = $translator; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $this->setLocale($event->getRequest()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + return; + } + + $this->setLocale($parentRequest); + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Locale listener + KernelEvents::REQUEST => array(array('onKernelRequest', 10)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } + + private function setLocale(Request $request) + { + try { + $this->translator->setLocale($request->getLocale()); + } catch (\InvalidArgumentException $e) { + $this->translator->setLocale($request->getDefaultLocale()); + } + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php new file mode 100644 index 0000000000..b96c7814ee --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -0,0 +1,55 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates Requests. + * + * @author Magnus Nordlander <magnus@fervo.se> + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + $request->getClientIps(); + } + + $request->getHost(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array( + array('onKernelRequest', 256), + ), + ); + } +} diff --git a/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 0000000000..79d8639a5f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * AccessDeniedHttpException. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Christophe Coevoet <stof@notk.org> + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(403, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php new file mode 100644 index 0000000000..5f68172a8d --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * BadRequestHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(400, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php new file mode 100644 index 0000000000..34d738ed12 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ConflictHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class ConflictHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(409, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Exception/GoneHttpException.php new file mode 100644 index 0000000000..16ea223fae --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/GoneHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * GoneHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class GoneHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(410, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 0000000000..e8e3760583 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\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; + } + + /** + * Set response headers. + * + * @param array $headers Response headers + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000000..8aa50a9f41 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,34 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith <kris@symfony.com> + */ +interface HttpExceptionInterface +{ + /** + * Returns the status code. + * + * @return int An HTTP response status code + */ + public function getStatusCode(); + + /** + * Returns response headers. + * + * @return array Response headers + */ + public function getHeaders(); +} diff --git a/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 0000000000..0c4b9431f4 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * LengthRequiredHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class LengthRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(411, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 0000000000..78dd26bf0f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * MethodNotAllowedHttpException. + * + * @author Kris Wallsmith <kris@symfony.com> + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param array $allow An array of allowed methods + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('Allow' => strtoupper(implode(', ', $allow))); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 0000000000..cc6be4ba45 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotAcceptableHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(406, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php new file mode 100644 index 0000000000..4639e379b9 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotFoundHttpException. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(404, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 0000000000..9df0e7b49a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionFailedHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class PreconditionFailedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(412, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 0000000000..08ebca2241 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,34 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionRequiredHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(428, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 0000000000..32b9e2d261 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ServiceUnavailableHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 0000000000..ab86e0920b --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * TooManyRequestsHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 0000000000..0dfe42db97 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnauthorizedHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $challenge WWW-Authenticate challenge string + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('WWW-Authenticate' => $challenge); + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php new file mode 100644 index 0000000000..eb13f563f6 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnprocessableEntityHttpException. + * + * @author Steve Hutchins <hutchinsteve@gmail.com> + */ +class UnprocessableEntityHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(422, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 0000000000..a9d8fa086f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnsupportedMediaTypeHttpException. + * + * @author Ben Ramsey <ben@benramsey.com> + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(415, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php new file mode 100644 index 0000000000..0d4d26b676 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -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\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Implements Surrogate rendering strategy. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer +{ + private $surrogate; + private $inlineStrategy; + private $signer; + + /** + * Constructor. + * + * The "fallback" strategy when surrogate is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param SurrogateInterface $surrogate An Surrogate instance + * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported + * @param UriSigner $signer + */ + public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + { + $this->surrogate = $surrogate; + $this->inlineStrategy = $inlineStrategy; + $this->signer = $signer; + } + + /** + * {@inheritdoc} + * + * Note that if the current Request has no surrogate capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning the surrogate tag + * + * Note, that not all surrogate strategies support all options. For now + * 'alt' and 'comment' are only supported by ESI. + * + * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface + */ + public function render($uri, Request $request, array $options = array()) + { + if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { + @trigger_error('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated since version 3.1, and will be removed in 4.0. Use a different rendering strategy or pass scalar values.', E_USER_DEPRECATED); + } + + return $this->inlineStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateSignedFragmentUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateSignedFragmentUri($alt, $request); + } + + $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + + return new Response($tag); + } + + private function generateSignedFragmentUri($uri, Request $request) + { + if (null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); + + return substr($fragmentUri, strlen($request->getSchemeAndHttpHost())); + } + + private function containsNonScalars(array $values) + { + foreach ($values as $value) { + if (is_array($value) && $this->containsNonScalars($value)) { + return true; + } elseif (!is_scalar($value) && null !== $value) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 0000000000..a4570e3beb --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php new file mode 100644 index 0000000000..0d0a042460 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php @@ -0,0 +1,118 @@ +<?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\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + private $debug; + private $renderers = array(); + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(RequestStack $requestStack, array $renderers = array(), $debug = false) + { + $this->requestStack = $requestStack; + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + $this->debug = $debug; + } + + /** + * Adds a renderer. + * + * @param FragmentRendererInterface $renderer A FragmentRendererInterface instance + */ + public function addRenderer(FragmentRendererInterface $renderer) + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $renderer The renderer name + * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \LogicException when no master request is being handled + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (!$request = $this->requestStack->getCurrentRequest()) { + throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @param Response $response A Response instance + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response) + { + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 0000000000..b177c3ac12 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + * + * @return Response A Response instance + */ + public function render($uri, Request $request, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 0000000000..ec2a40716a --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,167 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Loader\ExistsLoaderInterface; + +/** + * 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|Environment $templating An EngineInterface or a Twig 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|Environment|null $templating An EngineInterface or an Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of Twig\Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + } + + /** + * Checks if a templating engine has been set. + * + * @return bool true if the templating engine has been set, false otherwise + */ + public function hasTemplating() + { + return null !== $this->templating; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render($uri, Request $request, array $options = array()) + { + if ($uri instanceof ControllerReference) { + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), strlen($request->getSchemeAndHttpHost())); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $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) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, $flags, $this->charset, false), + htmlspecialchars($value, $flags, $this->charset, false) + ); + } + } + + return new Response(sprintf('<hx:include src="%s"%s>%s</hx:include>', $uri, $renderedAttributes, $content)); + } + + /** + * @param string $template + * + * @return bool + */ + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + try { + return $this->templating->exists($template); + } catch (\InvalidArgumentException $e) { + return false; + } + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { + return $loader->exists($template); + } + + try { + if (method_exists($loader, 'getSourceContext')) { + $loader->getSourceContext($template); + } else { + $loader->getSource($template); + } + + return true; + } catch (LoaderError $e) { + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 0000000000..437b40bf95 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,158 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + private $kernel; + private $dispatcher; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel A HttpKernelInterface instance + * @param EventDispatcherInterface $dispatcher A EventDispatcherInterface instance + */ + public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + { + $this->kernel = $kernel; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render($uri, Request $request, array $options = array()) + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = array(); + + // The request format and locale might have been overridden by the user + foreach (array('_format', '_locale') as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request, false, false); + + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is simply ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } + + // let's clean up the output buffers that were created by the sub-request + Response::closeOutputBuffers($level, false); + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest($uri, Request $request) + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + // Override the arguments to emulate a sub-request. + // Sub-request object will point to localhost as client ip and real client ip + // will be included into trusted header for client ip + try { + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $currentXForwardedFor = $request->headers->get('X_FORWARDED_FOR', ''); + + $server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } elseif (method_exists(Request::class, 'getTrustedHeaderName') && $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP, false)) { + $currentXForwardedFor = $request->headers->get($trustedHeaderName, ''); + + $server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } + } catch (\InvalidArgumentException $e) { + // Do nothing + } + + $server['REMOTE_ADDR'] = '127.0.0.1'; + unset($server['HTTP_IF_MODIFIED_SINCE']); + unset($server['HTTP_IF_NONE_MATCH']); + + $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + if ($session = $request->getSession()) { + $subRequest->setSession($session); + } + + return $subRequest; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 0000000000..d7eeb89a6b --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,90 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + private $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @param string $path The path + * + * @see FragmentListener + */ + public function setFragmentPath($path) + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * + * @return string A fragment URI + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true) + { + if ($strict) { + $this->checkNonScalar($reference->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($reference->attributes['_format'])) { + $reference->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($reference->attributes['_locale'])) { + $reference->attributes['_locale'] = $request->getLocale(); + } + + $reference->attributes['_controller'] = $reference->controller; + + $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); + + $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); + + if ($absolute) { + return $request->getUriForPath($path); + } + + return $request->getBaseUrl().$path; + } + + private function checkNonScalar($values) + { + foreach ($values as $key => $value) { + if (is_array($value)) { + $this->checkNonScalar($value); + } elseif (!is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php new file mode 100644 index 0000000000..45e7122f05 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the SSI rendering strategy. + * + * @author Sebastian Krebs <krebs.seb@gmail.com> + */ +class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php new file mode 100644 index 0000000000..af94bea9ba --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -0,0 +1,138 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Abstract class implementing Surrogate capabilities to Request and Response instances. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Robin Chalas <robin.chalas@gmail.com> + */ +abstract class AbstractSurrogate implements SurrogateInterface +{ + protected $contentTypes; + protected $phpEscapeMap = array( + array('<?', '<%', '<s', '<S'), + array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'), + ); + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for Surrogate 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 ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * {@inheritdoc} + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName()))); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = sprintf('symfony="%s/1.0"', strtoupper($this->getName())); + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * {@inheritdoc} + */ + public function needsParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + $pattern = sprintf('#content="[^"]*%s/1.0[^"]*"#', strtoupper($this->getName())); + + return (bool) preg_match($pattern, $control); + } + + /** + * {@inheritdoc} + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, Request::METHOD_GET, 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; + } + } + } + + /** + * Remove the Surrogate from the Surrogate-Control header. + * + * @param Response $response + */ + protected function removeFromControl(Response $response) + { + if (!$response->headers->has('Surrogate-Control')) { + return; + } + + $value = $response->headers->get('Surrogate-Control'); + $upperName = strtoupper($this->getName()); + + if (sprintf('content="%s/1.0"', $upperName) == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match(sprintf('#,\s*content="%s/1.0"#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#,\s*content="%s/1.0"#', $upperName), '', $value)); + } elseif (preg_match(sprintf('#content="%s/1.0",\s*#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 0000000000..d09907ea62 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * 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 extends AbstractSurrogate +{ + public function getName() + { + return 'esi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), '<esi:include')) { + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * {@inheritdoc} + */ + 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; + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content); + $content = preg_replace('#<esi\:comment[^>]+>#s', '', $content); + + $chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), + isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 0000000000..be2bc63dda --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,737 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + private $kernel; + private $store; + private $request; + private $surrogate; + private $surrogateCacheStrategy; + private $options = array(); + private $traces = array(); + + /** + * Constructor. + * + * The available options are: + * + * * debug: If true, the traces are added as a HTTP header to ease debugging + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + * + * @param HttpKernelInterface $kernel An HttpKernelInterface instance + * @param StoreInterface $store A Store instance + * @param SurrogateInterface $surrogate A SurrogateInterface instance + * @param array $options An array of options + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) + { + $this->store = $store; + $this->kernel = $kernel; + $this->surrogate = $surrogate; + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function(array($this->store, 'cleanup')); + + $this->options = array_merge(array( + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => array('Authorization', 'Cookie'), + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + ), $options); + } + + /** + * Gets the current store. + * + * @return StoreInterface $store A StoreInterface instance + */ + public function getStore() + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + * + * @return array An array of events + */ + public function getTraces() + { + return $this->traces; + } + + /** + * Returns a log message for the events of the last request processing. + * + * @return string A log message + */ + public function getLog() + { + $log = array(); + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the master request. + * + * @return Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Gets the Kernel instance. + * + * @return HttpKernelInterface An HttpKernelInterface instance + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets the Surrogate instance. + * + * @return SurrogateInterface A Surrogate instance + * + * @throws \LogicException + */ + public function getSurrogate() + { + return $this->surrogate; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->traces = array(); + $this->request = $request; + if (null !== $this->surrogate) { + $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); + } + } + + $this->traces[$this->getTraceKey($request)] = array(); + + if (!$request->isMethodSafe(false)) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { + $response = $this->pass($request, $catch); + } elseif ($this->options['allow_reload'] && $request->isNoCache()) { + /* + If allow_reload is configured and the client requests "Cache-Control: no-cache", + reload the cache by fetching a fresh response and caching it (if possible). + */ + $this->record($request, 'reload'); + $response = $this->fetch($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { + $response->headers->set('X-Symfony-Cache', $this->getLog()); + } + + if (null !== $this->surrogate) { + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->surrogateCacheStrategy->update($response); + } else { + $this->surrogateCacheStrategy->add($response); + } + } + + $response->prepare($request); + + $response->isNotModified($request); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function pass(Request $request, $catch = false) + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, $catch = false) + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (array('Location', 'Content-Location') as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + */ + protected function lookup(Request $request, $catch = false) + { + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance to validate + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function validate(Request $request, Response $entry, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // add our cached last-modified validator + $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); + $requestEtags = $request->getETags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('if_none_match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Unconditionally fetches a fresh response from the backend and + * stores it in the cache if is cacheable. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + */ + protected function fetch(Request $request, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // avoid that the backend sends no content + $subRequest->headers->remove('if_modified_since'); + $subRequest->headers->remove('if_none_match'); + + $response = $this->forward($subRequest, $catch); + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * All backend requests (cache passes, fetches, cache validations) + * run through this method. + * + * @param Request $request A Request instance + * @param bool $catch Whether to catch exceptions or not + * @param Response $entry A Response instance (the stale entry if present, null otherwise) + * + * @return Response A Response instance + */ + protected function forward(Request $request, $catch = false, Response $entry = null) + { + if ($this->surrogate) { + $this->surrogate->addSurrogateCapability($request); + } + + // modify the X-Forwarded-For header if needed + $forwardedFor = $request->headers->get('X-Forwarded-For'); + if ($forwardedFor) { + $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); + } else { + $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); + } + + // fix the client IP address by setting it to 127.0.0.1 as HttpCache + // is always called from the same process as the backend. + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // make sure HttpCache is a trusted proxy + if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { + $trustedProxies[] = '127.0.0.1'; + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + } + + // 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; + } + } + + /* + RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate + clock MUST NOT send a "Date" header, although it MUST send one in most other cases + except for 1xx or 5xx responses where it MAY do so. + + Anyway, a client that received a message without a "Date" header MUST add it. + */ + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + + $this->processResponseBody($request, $response); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry if fresh enough, false otherwise + */ + protected function isFreshEnough(Request $request, Response $entry) + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry) + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request); + + if (true === $lock) { + // we have the lock, call the backend + return false; + } + + // there is already another process calling the backend + + // May we serve a stale response? + if ($this->mayServeStaleWhileRevalidate($entry)) { + $this->record($request, 'stale-while-revalidate'); + + return true; + } + + // wait for the lock to be released + if ($this->waitForLock($request)) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + /** + * 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 + */ + private function restoreResponseBody(Request $request, Response $response) + { + if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { + $response->setContent(null); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Body-File'); + + return; + } + + if ($response->headers->has('X-Body-Eval')) { + ob_start(); + + if ($response->headers->has('X-Body-File')) { + include $response->headers->get('X-Body-File'); + } else { + eval('; ?>'.$response->getContent().'<?php ;'); + } + + $response->setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response) + { + if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { + $this->surrogate->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + * + * @param Request $request A Request instance + * + * @return bool true if the Request is private, false otherwise + */ + private function isPrivateRequest(Request $request) + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + * + * @param Request $request A Request instance + * @param string $event The event name + */ + private function record(Request $request, $event) + { + $this->traces[$this->getTraceKey($request)][] = $event; + } + + /** + * Calculates the key we use in the "trace" array for a given request. + * + * @param Request $request + * + * @return string + */ + private function getTraceKey(Request $request) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + + return $request->getMethod().' '.$path; + } + + /** + * Checks whether the given (cached) response may be served as "stale" when a revalidation + * is currently in progress. + * + * @param Response $entry + * + * @return bool True when the stale response may be served, false otherwise. + */ + private function mayServeStaleWhileRevalidate(Response $entry) + { + $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + + if (null === $timeout) { + $timeout = $this->options['stale_while_revalidate']; + } + + return abs($entry->getTtl()) < $timeout; + } + + /** + * Waits for the store to release a locked entry. + * + * @param Request $request The request to wait for + * + * @return bool True if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded. + */ + private function waitForLock(Request $request) + { + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + return $wait < 5000000; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php new file mode 100644 index 0000000000..027b2b1761 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -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. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the surrogates has validation cache strategy. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ResponseCacheStrategy implements ResponseCacheStrategyInterface +{ + private $cacheable = true; + private $embeddedResponses = 0; + private $ttls = array(); + private $maxAges = array(); + private $isNotCacheableResponseEmbedded = false; + + /** + * {@inheritdoc} + */ + public function add(Response $response) + { + if (!$response->isFresh() || !$response->isCacheable()) { + $this->cacheable = false; + } else { + $maxAge = $response->getMaxAge(); + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $maxAge; + + if (null === $maxAge) { + $this->isNotCacheableResponseEmbedded = true; + } + } + + ++$this->embeddedResponses; + } + + /** + * {@inheritdoc} + */ + public function update(Response $response) + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + } + + if (!$response->isFresh()) { + $this->cacheable = false; + } + + if (!$this->cacheable) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + + return; + } + + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + + if ($this->isNotCacheableResponseEmbedded) { + $response->headers->removeCacheControlDirective('s-maxage'); + } elseif (null !== $maxAge = min($this->maxAges)) { + $response->setSharedMaxAge($maxAge); + $response->headers->set('Age', $maxAge - min($this->ttls)); + } + $response->setMaxAge(0); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php new file mode 100644 index 0000000000..d70c2e06ec --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -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; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface ResponseCacheStrategyInterface +{ + /** + * 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); +} diff --git a/vendor/symfony/http-kernel/HttpCache/Ssi.php b/vendor/symfony/http-kernel/HttpCache/Ssi.php new file mode 100644 index 0000000000..3178c33515 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.php @@ -0,0 +1,98 @@ +<?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; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs <krebs.seb@gmail.com> + */ +class Ssi extends AbstractSurrogate +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), '<!--#include')) { + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + } + } + + /** + * {@inheritdoc} + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + { + return sprintf('<!--#include virtual="%s" -->', $uri); + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have SSI tags in a plain text response + $content = $response->getContent(); + + $chunks = preg_split('#<!--\#include\s+(.*?)\s*-->#', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['virtual'])) { + throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); + } + + $chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, \'\', false) ?>'."\n", + var_export($options['virtual'], true) + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'SSI'); + + // remove SSI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 0000000000..c4d961e68f --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,508 @@ +<?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 + * + * @throws \RuntimeException + */ + public function __construct($root) + { + $this->root = $root; + if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->locks = array(); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, LOCK_UN); + fclose($lock); + } + + $this->locks = array(); + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @param Request $request A Request instance + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + return $path; + } + $h = fopen($path, 'cb'); + if (!flock($h, LOCK_EX | LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!file_exists($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'rb'); + flock($h, LOCK_EX | LOCK_NB, $wouldBlock); + flock($h, LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * 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; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return; + } + + list($req, $headers) = $match; + if (file_exists($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. + } + + /** + * 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'.hash('sha256', $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 && 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 bool 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 = str_replace('_', '-', 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 (!$entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @param string $url A URL + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge($url) + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + private function doPurge($url) + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (file_exists($path = $this->getPath($key))) { + 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 file_exists($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 bool + */ + private function save($key, $data) + { + $path = $this->getPath($key); + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + 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); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + protected function generateCacheKey(Request $request) + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * 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] = $this->generateCacheKey($request); + } + + /** + * 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); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php new file mode 100644 index 0000000000..ddc0c04ee3 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php @@ -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 bool|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 bool 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 bool true if lock exists, false otherwise + */ + public function isLocked(Request $request); + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge($url); + + /** + * Cleanups storage. + */ + public function cleanup(); +} diff --git a/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php new file mode 100644 index 0000000000..5d65fd65a4 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -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\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +interface SurrogateInterface +{ + /** + * Returns surrogate name. + * + * @return string + */ + public function getName(); + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy(); + + /** + * Checks that at least one surrogate has Surrogate capability. + * + * @param Request $request A Request instance + * + * @return bool true if one surrogate has Surrogate capability, false otherwise + */ + public function hasSurrogateCapability(Request $request); + + /** + * Adds Surrogate-capability to the given Request. + * + * @param Request $request A Request instance + */ + public function addSurrogateCapability(Request $request); + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. + * + * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + * + * @param Response $response A Response instance + */ + public function addSurrogateControl(Response $response); + + /** + * Checks that the Response needs to be parsed for Surrogate tags. + * + * @param Response $response A Response instance + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response); + + /** + * Renders a Surrogate tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = ''); + + /** + * Replaces a Response Surrogate 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); + + /** + * Handles a Surrogate from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param bool $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors); +} diff --git a/vendor/symfony/http-kernel/HttpKernel.php b/vendor/symfony/http-kernel/HttpKernel.php new file mode 100644 index 0000000000..8d55ccde1c --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.php @@ -0,0 +1,301 @@ +<?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\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +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\FinishRequestEvent; +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\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +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> + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected $dispatcher; + protected $resolver; + protected $requestStack; + private $argumentResolver; + + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + $this->requestStack = $requestStack ?: new RequestStack(); + $this->argumentResolver = $argumentResolver; + + if (null === $this->argumentResolver) { + @trigger_error(sprintf('As of 3.1 an %s is used to resolve arguments. In 4.0 the $argumentResolver becomes the %s if no other is provided instead of using the $resolver argument.', ArgumentResolverInterface::class, ArgumentResolver::class), E_USER_DEPRECATED); + // fallback in case of deprecations + $this->argumentResolver = $resolver; + } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + try { + return $this->handleRaw($request, $type); + } catch (\Exception $e) { + if ($e instanceof RequestExceptionInterface) { + $e = new BadRequestHttpException($e->getMessage(), $e); + } + if (false === $catch) { + $this->finishRequest($request, $type); + + throw $e; + } + + return $this->handleException($e, $request, $type); + } + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + } + + /** + * @throws \LogicException If the request stack is empty + * + * @internal + */ + public function terminateWithException(\Exception $exception) + { + if (!$request = $this->requestStack->getMasterRequest()) { + throw new \LogicException('Request stack is empty', 0, $exception); + } + + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @param Request $request A Request instance + * @param int $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) + { + $this->requestStack->push($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". The route is wrongly configured.', $request->getPathInfo())); + } + + $event = new FilterControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->argumentResolver->getArguments($request, $controller); + + $event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event); + $controller = $event->getController(); + $arguments = $event->getArguments(); + + // 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 int $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); + + $this->finishRequest($request, $type); + + return $event->getResponse(); + } + + /** + * Publishes the finish request event, then pop the request from the stack. + * + * Note that the order of the operations is important here, otherwise + * operations such as {@link RequestStack::getParentRequest()} can lead to + * weird results. + * + * @param Request $request + * @param int $type + */ + private function finishRequest(Request $request, $type) + { + $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); + $this->requestStack->pop(); + } + + /** + * Handles an exception by trying to convert it to a Response. + * + * @param \Exception $e An \Exception instance + * @param Request $request A Request instance + * @param int $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()) { + $this->finishRequest($request, $type); + + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if ($response->headers->has('X-Status-Code')) { + @trigger_error(sprintf('Using the X-Status-Code header is deprecated since version 3.3 and will be removed in 4.0. Use %s::allowCustomResponseCode() instead.', GetResponseForExceptionEvent::class), E_USER_DEPRECATED); + + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$event->isAllowingCustomResponseCode() && !$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; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/HttpKernelInterface.php new file mode 100644 index 0000000000..5050bfcfba --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelInterface.php @@ -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; + +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> + */ +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 int $type The type of the request + * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not + * + * @return Response A Response instance + * + * @throws \Exception When an Exception occurs during processing + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} diff --git a/vendor/symfony/http-kernel/Kernel.php b/vendor/symfony/http-kernel/Kernel.php new file mode 100644 index 0000000000..0372a86cf7 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,866 @@ +<?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\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\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; +use Symfony\Component\Config\Loader\GlobFileLoader; +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> + */ +abstract class Kernel implements KernelInterface, TerminableInterface +{ + /** + * @var BundleInterface[] + */ + protected $bundles = array(); + + protected $bundleMap; + protected $container; + protected $rootDir; + protected $environment; + protected $debug; + protected $booted = false; + protected $name; + protected $startTime; + protected $loadClassCache; + + private $projectDir; + + const VERSION = '3.3.6'; + const VERSION_ID = 30306; + const MAJOR_VERSION = 3; + const MINOR_VERSION = 3; + const RELEASE_VERSION = 6; + const EXTRA_VERSION = ''; + + const END_OF_MAINTENANCE = '01/2018'; + const END_OF_LIFE = '07/2018'; + + /** + * Constructor. + * + * @param string $environment The environment + * @param bool $debug Whether to enable debugging or not + */ + public function __construct($environment, $debug) + { + $this->environment = $environment; + $this->debug = (bool) $debug; + $this->rootDir = $this->getRootDir(); + $this->name = $this->getName(); + + if ($this->debug) { + $this->startTime = microtime(true); + } + } + + public function __clone() + { + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->booted = false; + $this->container = null; + } + + /** + * Boots the current kernel. + */ + 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} + */ + public function terminate(Request $request, Response $response) + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + /** + * {@inheritdoc} + */ + 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} + */ + 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} + */ + public function getBundles() + { + return $this->bundles; + } + + /** + * {@inheritdoc} + */ + 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]; + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle + */ + 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} + */ + public function getName() + { + if (null === $this->name) { + $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); + if (ctype_digit($this->name[0])) { + $this->name = '_'.$this->name; + } + } + + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->debug; + } + + /** + * {@inheritdoc} + */ + public function getRootDir() + { + if (null === $this->rootDir) { + $r = new \ReflectionObject($this); + $this->rootDir = dirname($r->getFileName()); + } + + return $this->rootDir; + } + + /** + * Gets the application root dir (path of the project's composer file). + * + * @return string The project root dir + */ + public function getProjectDir() + { + if (null === $this->projectDir) { + $r = new \ReflectionObject($this); + $dir = $rootDir = dirname($r->getFileName()); + while (!file_exists($dir.'/composer.json')) { + if ($dir === dirname($dir)) { + return $this->projectDir = $rootDir; + } + $dir = dirname($dir); + } + $this->projectDir = $dir; + } + + return $this->projectDir; + } + + /** + * {@inheritdoc} + */ + 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 + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->loadClassCache = array($name, $extension); + } + + /** + * @internal + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setClassCache(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + file_put_contents($this->getCacheDir().'/classes.map', sprintf('<?php return %s;', var_export($classes, true))); + } + + /** + * @internal + */ + public function setAnnotatedClassCache(array $annotatedClasses) + { + file_put_contents($this->getCacheDir().'/annotations.map', sprintf('<?php return %s;', var_export($annotatedClasses, true))); + } + + /** + * {@inheritdoc} + */ + public function getStartTime() + { + return $this->debug ? $this->startTime : -INF; + } + + /** + * {@inheritdoc} + */ + public function getCacheDir() + { + return $this->rootDir.'/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + */ + public function getLogDir() + { + return $this->rootDir.'/logs'; + } + + /** + * {@inheritdoc} + */ + public function getCharset() + { + return 'UTF-8'; + } + + /** + * @deprecated since version 3.3, to be removed in 4.0. + */ + protected function doLoadClassCache($name, $extension) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + 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 (!empty($directChildren) && count($diff = array_diff_key($directChildren, $this->bundles))) { + $diff = array_keys($diff); + + 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 $hierarchyBundle) { + $this->bundleMap[$hierarchyBundle] = $bundleMap; + array_pop($bundleMap); + } + } + } + + /** + * The extension point similar to the Bundle::build() method. + * + * Use this method to register compiler passes and manipulate the container during the building process. + * + * @param ContainerBuilder $container + */ + protected function build(ContainerBuilder $container) + { + } + + /** + * 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()) { + if ($this->debug) { + $collectedLogs = array(); + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = array_slice($backtrace, 1 + $i); + break; + } + } + + $collectedLogs[$message] = array( + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => $backtrace, + 'count' => 1, + ); + }); + } + + try { + $container = null; + $container = $this->buildContainer(); + $container->compile(); + } finally { + if ($this->debug) { + restore_error_handler(); + + file_put_contents($this->getCacheDir().'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); + file_put_contents($this->getCacheDir().'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } + } + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + $fresh = false; + } + + require_once $cache->getPath(); + + $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(); + $bundlesMetadata = array(); + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = get_class($bundle); + $bundlesMetadata[$name] = array( + 'parent' => $bundle->getParent(), + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ); + } + + return array_merge( + array( + 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.debug' => $this->debug, + 'kernel.name' => $this->name, + 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters(false) + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected function getEnvParameters() + { + if (0 === func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax to get the value of any environment variable from configuration files instead.', __METHOD__), E_USER_DEPRECATED); + } + + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + @trigger_error(sprintf('The support of special environment variables that start with SYMFONY__ (such as "%s") is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax instead to get the value of environment variables in configuration files.', $key), E_USER_DEPRECATED); + $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) && !is_dir($dir)) { + 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 AddAnnotatedClassesToCachePass($this)); + $container->addResource(new EnvParametersResource('SYMFONY__')); + + 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); + } + + $this->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(); + $container->getParameterBag()->add($this->getKernelParameters()); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { + $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') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { + $dumper->setProxyDumper(new ProxyDumper(md5($cache->getPath()))); + } + + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'debug' => $this->debug)); + + $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 GlobFileLoader($locator), + new DirectoryLoader($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); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while ($token[0] !== T_END_HEREDOC); + $rawChunk = ''; + } elseif (T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(array('/\n{2,}/S'), "\n", $token[1]); + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } + } + } + + $output .= $rawChunk; + + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + public function serialize() + { + return serialize(array($this->environment, $this->debug)); + } + + public function unserialize($data) + { + if (\PHP_VERSION_ID >= 70000) { + list($environment, $debug) = unserialize($data, array('allowed_classes' => false)); + } else { + list($environment, $debug) = unserialize($data); + } + + $this->__construct($environment, $debug); + } +} diff --git a/vendor/symfony/http-kernel/KernelEvents.php b/vendor/symfony/http-kernel/KernelEvents.php new file mode 100644 index 0000000000..3e961737b8 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelEvents.php @@ -0,0 +1,119 @@ +<?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> + */ +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. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseEvent") + * + * @var string + */ + 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. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent") + * + * @var string + */ + 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. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent") + * + * @var string + */ + 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. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerEvent") + * + * @var string + */ + const CONTROLLER = 'kernel.controller'; + + /** + * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. + * + * This event allows you to change the arguments that will be passed to + * the controller. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent") + * + * @var string + */ + const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + + /** + * 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. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterResponseEvent") + * + * @var string + */ + const RESPONSE = 'kernel.response'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * + * @Event("Symfony\Component\HttpKernel\Event\PostResponseEvent") + * + * @var string + */ + const TERMINATE = 'kernel.terminate'; + + /** + * The FINISH_REQUEST event occurs when a response was generated for a request. + * + * This event allows you to reset the global and environmental state of + * the application, when it was changed during the request. + * + * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") + * + * @var string + */ + const FINISH_REQUEST = 'kernel.finish_request'; +} diff --git a/vendor/symfony/http-kernel/KernelInterface.php b/vendor/symfony/http-kernel/KernelInterface.php new file mode 100644 index 0000000000..b8609b9ede --- /dev/null +++ b/vendor/symfony/http-kernel/KernelInterface.php @@ -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; + +use Symfony\Component\DependencyInjection\ContainerInterface; +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> + */ +interface KernelInterface extends HttpKernelInterface, \Serializable +{ + /** + * Returns an array of bundles to register. + * + * @return BundleInterface[] An array of bundle instances + */ + public function registerBundles(); + + /** + * Loads the container configuration. + * + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return BundleInterface[] An array of registered bundle instances + */ + public function getBundles(); + + /** + * Returns a bundle and optionally its descendants by its name. + * + * @param string $name Bundle name + * @param bool $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 + */ + 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 + * + * 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 bool $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 + */ + public function locateResource($name, $dir = null, $first = true); + + /** + * Gets the name of the kernel. + * + * @return string The kernel name + */ + public function getName(); + + /** + * Gets the environment. + * + * @return string The current environment + */ + public function getEnvironment(); + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug(); + + /** + * Gets the application root dir (path of the project's Kernel class). + * + * @return string The Kernel root dir + */ + public function getRootDir(); + + /** + * Gets the current container. + * + * @return ContainerInterface A ContainerInterface instance + */ + public function getContainer(); + + /** + * Gets the request start time (not available if debug is disabled). + * + * @return int The request start timestamp + */ + public function getStartTime(); + + /** + * Gets the cache directory. + * + * @return string The cache directory + */ + public function getCacheDir(); + + /** + * Gets the log directory. + * + * @return string The log directory + */ + public function getLogDir(); + + /** + * Gets the charset of the application. + * + * @return string The charset + */ + public function getCharset(); +} diff --git a/vendor/symfony/http-kernel/LICENSE b/vendor/symfony/http-kernel/LICENSE new file mode 100644 index 0000000000..17d16a1336 --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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. diff --git a/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php new file mode 100644 index 0000000000..5635a2184f --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -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 int The number of errors + */ + public function countErrors(); +} diff --git a/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php new file mode 100644 index 0000000000..bd8761f5dd --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,284 @@ +<?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) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = 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)) { + $values = str_getcsv($line); + list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values; + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) { + 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, + 'status_code' => $csvStatusCode, + ); + } + + 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; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException + */ + 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) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + // 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(), + 'status_code' => $profile->getStatusCode(), + ); + + 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(), + $profile->getStatusCode(), + )); + 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; + } + + 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->setStatusCode($data['status_code']); + $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; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Profiler/Profile.php new file mode 100644 index 0000000000..fab8b41da4 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,295 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\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; + private $statusCode; + + /** + * @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 + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return self + */ + 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 int The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->time; + } + + /** + * @param int The time + */ + public function setTime($time) + { + $this->time = $time; + } + + /** + * @param int $statusCode + */ + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Finds children profilers. + * + * @return self[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + * + * @param Profile $child + */ + 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 bool + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + public function __sleep() + { + return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode'); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 0000000000..f31e243770 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,270 @@ +<?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\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +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 bool + */ + 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|false 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 bool + */ + public function saveProfile(Profile $profile) + { + // late collect + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof LateDataCollectorInterface) { + $collector->lateCollect(); + } + } + + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.', array('configured_storage' => get_class($this->storage))); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge() + { + $this->storage->purge(); + } + + /** + * 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 + * @param string $statusCode The request status code + * + * @return array An array of tokens + * + * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats + */ + public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null) + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); + } + + /** + * 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(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatusCode()); + try { + $profile->setIp($request->getClientIp()); + } catch (ConflictingHeadersException $e) { + $profile->setIp('Unknown'); + } + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // we need to clone for sub-requests + $profile->addCollector(clone $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 bool + */ + 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]; + } + + private function getTimestamp($value) + { + if (null === $value || '' == $value) { + return; + } + + try { + $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception $e) { + return; + } + + return $value->getTimestamp(); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 0000000000..ea72af2314 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -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 bool Write operation successful + */ + public function write(Profile $profile); + + /** + * Purges all data from the database. + */ + public function purge(); +} diff --git a/vendor/symfony/http-kernel/README.md b/vendor/symfony/http-kernel/README.md new file mode 100644 index 0000000000..cc5e74b6bc --- /dev/null +++ b/vendor/symfony/http-kernel/README.md @@ -0,0 +1,16 @@ +HttpKernel Component +==================== + +The HttpKernel component provides a structured process for converting a Request +into a Response by making use of the EventDispatcher component. It's flexible +enough to create a full-stack framework (Symfony), a micro-framework (Silex) or +an advanced CMS system (Drupal). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_kernel/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-kernel/TerminableInterface.php b/vendor/symfony/http-kernel/TerminableInterface.php new file mode 100644 index 0000000000..d55a15b87e --- /dev/null +++ b/vendor/symfony/http-kernel/TerminableInterface.php @@ -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; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano <j.boggiano@seld.be> + * @author Pierre Minnieur <pierre.minnieur@sensiolabs.de> + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + */ + public function terminate(Request $request, Response $response); +} diff --git a/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php new file mode 100644 index 0000000000..eeefccab81 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php @@ -0,0 +1,100 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Bundle; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle\ExtensionNotValidBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle\ExtensionAbsentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand; + +class BundleTest extends TestCase +{ + public function testGetContainerExtension() + { + $bundle = new ExtensionPresentBundle(); + + $this->assertInstanceOf( + 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection\ExtensionPresentExtension', + $bundle->getContainerExtension() + ); + } + + public function testRegisterCommands() + { + $cmd = new FooCommand(); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('add')->with($this->equalTo($cmd)); + + $bundle = new ExtensionPresentBundle(); + $bundle->registerCommands($app); + + $bundle2 = new ExtensionAbsentBundle(); + + $this->assertNull($bundle2->registerCommands($app)); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface + */ + public function testGetContainerExtensionWithInvalidClass() + { + $bundle = new ExtensionNotValidBundle(); + $bundle->getContainerExtension(); + } + + public function testHttpKernelRegisterCommandsIgnoresCommandsThatAreRegisteredAsServices() + { + $container = new ContainerBuilder(); + $container->register('console.command.symfony_component_httpkernel_tests_fixtures_extensionpresentbundle_command_foocommand', 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand'); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + // add() is never called when the found command classes are already registered as services + $application->expects($this->never())->method('add'); + + $bundle = new ExtensionPresentBundle(); + $bundle->setContainer($container); + $bundle->registerCommands($application); + } + + public function testBundleNameIsGuessedFromClass() + { + $bundle = new GuessedNameBundle(); + + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('GuessedNameBundle', $bundle->getName()); + } + + public function testBundleNameCanBeExplicitlyProvided() + { + $bundle = new NamedBundle(); + + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + } +} + +class NamedBundle extends Bundle +{ + public function __construct() + { + $this->name = 'ExplicitlyNamedBundle'; + } +} + +class GuessedNameBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php new file mode 100644 index 0000000000..1bc853349f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; + +class ChainCacheClearerTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_clearer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectClearersInConstructor() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(array($clearer)); + $chainClearer->clear(self::$cacheDir); + } + + public function testInjectClearerUsingAdd() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(); + $chainClearer->add($clearer); + $chainClearer->clear(self::$cacheDir); + } + + protected function getMockClearer() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface')->getMock(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php new file mode 100644 index 0000000000..a5d9b6ef4d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php @@ -0,0 +1,69 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Psr\Cache\CacheItemPoolInterface; + +class Psr6CacheClearerTest extends TestCase +{ + public function testClearPoolsInjectedInConstructor() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clear(''); + } + + public function testClearPool() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clearPool('pool'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cache pool not found: unknown + */ + public function testClearPoolThrowsExceptionOnUnreferencedPool() + { + (new Psr6CacheClearer())->clearPool('unknown'); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer::addPool() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead. + */ + public function testClearPoolsInjectedByAdder() + { + $pool1 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool1 + ->expects($this->once()) + ->method('clear'); + + $pool2 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool2 + ->expects($this->once()) + ->method('clear'); + + $clearer = new Psr6CacheClearer(array('pool1' => $pool1)); + $clearer->addPool($pool2); + $clearer->clear(''); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 0000000000..d07ade303f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,101 @@ +<?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\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; + +class CacheWarmerAggregateTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 0000000000..05666cb0dc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,68 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertFileExists(self::$cacheFile); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/ClientTest.php b/vendor/symfony/http-kernel/Tests/ClientTest.php new file mode 100644 index 0000000000..1ac72c7359 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ClientTest.php @@ -0,0 +1,183 @@ +<?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\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpKernel\Tests\Fixtures\TestClient; + +/** + * @group time-sensitive + */ +class ClientTest extends TestCase +{ + public function testDoRequest() + { + $client = new Client(new TestHttpKernel()); + + $client->request('GET', '/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $client->getRequest()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $client->getResponse()); + + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertEquals('www.example.com', $client->getRequest()->getHost(), '->doRequest() uses the request handler to make the request'); + + $client->request('GET', 'http://www.example.com/?parameter=http://google.com'); + $this->assertEquals('http://www.example.com/?parameter='.urlencode('http://google.com'), $client->getRequest()->getUri(), '->doRequest() uses the request handler to make the request'); + } + + public function testGetScript() + { + $client = new TestClient(new TestHttpKernel()); + $client->insulate(); + $client->request('GET', '/'); + + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->getScript() returns a script that uses the request handler to make the request'); + } + + public function testFilterResponseConvertsCookies() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $expected = array( + 'foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + 'foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + ); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie(new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + $this->assertEquals($expected, $domResponse->getHeader('Set-Cookie', false)); + } + + public function testFilterResponseSupportsStreamedResponses() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $response = new StreamedResponse(function () { + echo 'foo'; + }); + + $domResponse = $m->invoke($client, $response); + $this->assertEquals('foo', $domResponse->getContent()); + } + + public function testUploadedFile() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + $target = sys_get_temp_dir().'/sf.moved.file'; + @unlink($target); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $files = array( + array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 123, 'error' => UPLOAD_ERR_OK), + new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true), + ); + + $file = null; + foreach ($files as $file) { + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files['foo']; + + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('123', $file->getClientSize()); + $this->assertTrue($file->isValid()); + } + + $file->move(dirname($target), basename($target)); + + $this->assertFileExists($target); + unlink($target); + } + + public function testUploadedFileWhenNoFileSelected() + { + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = array('tmp_name' => '', 'name' => '', 'type' => '', 'size' => 0, 'error' => UPLOAD_ERR_NO_FILE); + + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + $this->assertNull($files['foo']); + } + + public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = $this + ->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') + ->setConstructorArgs(array($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true)) + ->setMethods(array('getSize')) + ->getMock() + ; + + $file->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(INF)) + ; + + $client->request('POST', '/', array(), array($file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files[0]; + + $this->assertFalse($file->isValid()); + $this->assertEquals(UPLOAD_ERR_INI_SIZE, $file->getError()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals(0, $file->getClientSize()); + + unlink($source); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php new file mode 100644 index 0000000000..6fe8a67568 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php @@ -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\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; + +class EnvParametersResourceTest extends TestCase +{ + protected $prefix = '__DUMMY_'; + protected $initialEnv; + protected $resource; + + protected function setUp() + { + $this->initialEnv = array( + $this->prefix.'1' => 'foo', + $this->prefix.'2' => 'bar', + ); + + foreach ($this->initialEnv as $key => $value) { + $_SERVER[$key] = $value; + } + + $this->resource = new EnvParametersResource($this->prefix); + } + + protected function tearDown() + { + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + unset($_SERVER[$key]); + } + } + } + + public function testGetResource() + { + $this->assertSame( + array('prefix' => $this->prefix, 'variables' => $this->initialEnv), + $this->resource->getResource(), + '->getResource() returns the resource' + ); + } + + public function testToString() + { + $this->assertSame( + serialize(array('prefix' => $this->prefix, 'variables' => $this->initialEnv)), + (string) $this->resource + ); + } + + public function testIsFreshNotChanged() + { + $this->assertTrue( + $this->resource->isFresh(time()), + '->isFresh() returns true if the variables have not changed' + ); + } + + public function testIsFreshValueChanged() + { + reset($this->initialEnv); + $_SERVER[key($this->initialEnv)] = 'baz'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been changed' + ); + } + + public function testIsFreshValueRemoved() + { + reset($this->initialEnv); + unset($_SERVER[key($this->initialEnv)]); + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been removed' + ); + } + + public function testIsFreshValueAdded() + { + $_SERVER[$this->prefix.'3'] = 'foo'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been added' + ); + } + + public function testSerializeUnserialize() + { + $this->assertEquals($this->resource, unserialize(serialize($this->resource))); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php new file mode 100644 index 0000000000..6265f02755 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php @@ -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\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\FileLocator; + +class FileLocatorTest extends TestCase +{ + public function testLocate() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', null, true) + ->will($this->returnValue('/bundle-name/some/path')); + $locator = new FileLocator($kernel); + $this->assertEquals('/bundle-name/some/path', $locator->locate('@BundleName/some/path')); + + $kernel + ->expects($this->never()) + ->method('locateResource'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('LogicException'); + $locator->locate('/some/path'); + } + + public function testLocateWithGlobalResourcePath() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', '/global/resource/path', false); + + $locator = new FileLocator($kernel, '/global/resource/path'); + $locator->locate('@BundleName/some/path', null, false); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php new file mode 100644 index 0000000000..0804139030 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php @@ -0,0 +1,349 @@ +<?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\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingRequest; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingSession; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ArgumentResolverTest extends TestCase +{ + /** @var ArgumentResolver */ + private static $resolver; + + public static function setUpBeforeClass() + { + $factory = new ArgumentMetadataFactory(); + + self::$resolver = new ArgumentResolver($factory); + } + + public function testDefaultState() + { + $this->assertEquals(self::$resolver, new ArgumentResolver()); + $this->assertNotEquals(self::$resolver, new ArgumentResolver(null, array(new RequestAttributeValueResolver()))); + } + + public function testGetArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFoo'); + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + } + + public function testGetArgumentsReturnsEmptyArrayWhenNoArguments() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithoutArguments'); + + $this->assertEquals(array(), self::$resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + } + + public function testGetArgumentsUsesDefaultValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + } + + public function testGetArgumentsOverrideDefaultValueByRequestAttribute() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'bar'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + } + + public function testGetArgumentsFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsUsesDefaultValueFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromInvokableObject() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller)); + + // Test default bar overridden by request attribute + $request->attributes->set('bar', 'bar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromFunctionName() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = __NAMESPACE__.'\controller_function'; + + $this->assertEquals(array('foo', 'foobar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFailsOnUnresolvedValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerWithFooBarFoobar'); + + try { + self::$resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + } + + public function testGetArgumentsInjectsRequest() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + public function testGetArgumentsInjectsExtendingRequest() + { + $request = ExtendingRequest::create('/'); + $controller = array(new self(), 'controllerWithExtendingRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request when extended'); + } + + /** + * @requires PHP 5.6 + */ + public function testGetVariadicArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + + $this->assertEquals(array('foo', 'foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetVariadicArgumentsWithoutArrayInRequest() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array(new VariadicController(), 'action'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetArgumentWithoutArray() + { + $factory = new ArgumentMetadataFactory(); + $valueResolver = $this->getMockBuilder(ArgumentValueResolverInterface::class)->getMock(); + $resolver = new ArgumentResolver($factory, array($valueResolver)); + + $valueResolver->expects($this->any())->method('supports')->willReturn(true); + $valueResolver->expects($this->any())->method('resolve')->willReturn('foo'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array($this, 'controllerWithFooAndDefaultBar'); + $resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithFoo'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArgumentsWithDefaults() + { + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array(null, null, 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArguments() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithExtendedSession() + { + $session = new ExtendingSession(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSessionInterface'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithImplementation() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchOnNull() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerWithFoo($foo) + { + } + + public function controllerWithoutArguments() + { + } + + protected function controllerWithFooAndDefaultBar($foo, $bar = null) + { + } + + protected function controllerWithFooBarFoobar($foo, $bar, $foobar) + { + } + + protected function controllerWithRequest(Request $request) + { + } + + protected function controllerWithExtendingRequest(ExtendingRequest $request) + { + } + + protected function controllerWithSession(Session $session) + { + } + + protected function controllerWithSessionInterface(SessionInterface $session) + { + } + + protected function controllerWithExtendingSession(ExtendingSession $session) + { + } +} + +function controller_function($foo, $foobar) +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php new file mode 100644 index 0000000000..30b535e825 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php @@ -0,0 +1,148 @@ +<?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\Tests\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; + +class ContainerControllerResolverTest extends ControllerResolverTest +{ + public function testGetControllerService() + { + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo:controllerMethod1'); + + $controller = $resolver->getController($request); + + $this->assertInstanceOf(get_class($this), $controller[0]); + $this->assertSame('controllerMethod1', $controller[1]); + } + + public function testGetControllerInvokableService() + { + $invokableController = new InvokableController('bar'); + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with('foo') + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo'); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + public function testGetControllerInvokableServiceWithClassNameAsName() + { + $invokableController = new InvokableController('bar'); + $className = __NAMESPACE__.'\InvokableController'; + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with($className) + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with($className) + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', $className); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + // All this logic needs to be duplicated, since calling parent::testGetControllerOnNonUndefinedFunction will override the expected excetion and not use the regex + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessageRegExp($exceptionMessage); + } else { + $this->setExpectedExceptionRegExp($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array('foo', \LogicException::class, '/Unable to parse the controller name "foo"\./'), + array('oof::bar', \InvalidArgumentException::class, '/Class "oof" does not exist\./'), + array('stdClass', \LogicException::class, '/Unable to parse the controller name "stdClass"\./'), + array( + 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar', + \InvalidArgumentException::class, + '/.?[cC]ontroller(.*?) for URI "\/" is not callable\.( Expected method(.*) Available methods)?/', + ), + ); + } + + protected function createControllerResolver(LoggerInterface $logger = null, ContainerInterface $container = null) + { + if (!$container) { + $container = $this->createMockContainer(); + } + + return new ContainerControllerResolver($container, $logger); + } + + protected function createMockContainer() + { + return $this->getMockBuilder(ContainerInterface::class)->getMock(); + } +} + +class InvokableController +{ + public function __construct($bar) // mandatory argument to prevent automatic instantiation + { + } + + public function __invoke() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php new file mode 100644 index 0000000000..190e15ad67 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -0,0 +1,331 @@ +<?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\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ControllerResolverTest extends TestCase +{ + public function testGetControllerWithoutControllerParameter() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once())->method('warning')->with('Unable to look for the controller as the "_controller" parameter is missing.'); + $resolver = $this->createControllerResolver($logger); + + $request = Request::create('/'); + $this->assertFalse($resolver->getController($request), '->getController() returns false when the request has no _controller attribute'); + } + + public function testGetControllerWithLambda() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $lambda = function () {}); + $controller = $resolver->getController($request); + $this->assertSame($lambda, $controller); + } + + public function testGetControllerWithObjectAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $this); + $controller = $resolver->getController($request); + $this->assertSame($this, $controller); + } + + public function testGetControllerWithObjectAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array($this, 'controllerMethod1')); + $controller = $resolver->getController($request); + $this->assertSame(array($this, 'controllerMethod1'), $controller); + } + + public function testGetControllerWithClassAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4')); + $controller = $resolver->getController($request); + $this->assertSame(array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4'), $controller); + } + + public function testGetControllerWithObjectAndMethodAsString() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::controllerMethod1'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller[0], '->getController() returns a PHP callable'); + } + + public function testGetControllerWithClassAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetControllerOnObjectWithoutInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', new \stdClass()); + $resolver->getController($request); + } + + public function testGetControllerWithFunction() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'); + $controller = $resolver->getController($request); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function', $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessage($exceptionMessage); + } else { + $this->setExpectedException($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array(1, 'InvalidArgumentException', 'Unable to find controller "1".'), + array('foo', 'InvalidArgumentException', 'Unable to find controller "foo".'), + array('oof::bar', 'InvalidArgumentException', 'Class "oof" does not exist.'), + array('stdClass', 'InvalidArgumentException', 'Unable to find controller "stdClass".'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'), + ); + } + + /** + * @group legacy + */ + public function testGetArguments() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $controller = array(new self(), 'testGetArguments'); + $this->assertEquals(array(), $resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod1'); + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod2'); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller)); + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'; + $this->assertEquals(array('foo', 'foobar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerMethod3'); + + try { + $resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + + $request = Request::create('/'); + $controller = array(new self(), 'controllerMethod5'); + $this->assertEquals(array($request), $resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + /** + * @requires PHP 5.6 + * @group legacy + */ + public function testGetVariadicArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + $this->assertEquals(array('foo', 'foo', 'bar'), $resolver->getArguments($request, $controller)); + } + + public function testCreateControllerCanReturnAnyCallable() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolver')->setMethods(array('createController'))->getMock(); + $mock->expects($this->once())->method('createController')->will($this->returnValue('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function')); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'foobar'); + $mock->getController($request); + } + + /** + * @expectedException \RuntimeException + * @group legacy + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $resolver = new ControllerResolver(); + $request = Request::create('/'); + + $controller = array($this, 'controllerMethod1'); + + $resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArgumentsWithDefaults() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array(null, null, 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + protected function createControllerResolver(LoggerInterface $logger = null) + { + return new ControllerResolver($logger); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerMethod1($foo) + { + } + + protected function controllerMethod2($foo, $bar = null) + { + } + + protected function controllerMethod3($foo, $bar, $foobar) + { + } + + protected static function controllerMethod4() + { + } + + protected function controllerMethod5(Request $request) + { + } +} + +function some_controller_function($foo, $foobar) +{ +} + +class ControllerTest +{ + public function publicAction() + { + } + + private function privateAction() + { + } + + protected function protectedAction() + { + } + + public static function staticAction() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php new file mode 100644 index 0000000000..b4b449f358 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -0,0 +1,148 @@ +<?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\Tests\ControllerMetadata; + +use Fake\ImportedAndFake; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; + +class ArgumentMetadataFactoryTest extends TestCase +{ + /** + * @var ArgumentMetadataFactory + */ + private $factory; + + protected function setUp() + { + $this->factory = new ArgumentMetadataFactory(); + } + + public function testSignature1() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature1')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, false, null), + new ArgumentMetadata('bar', 'array', false, false, null), + new ArgumentMetadata('baz', 'callable', false, false, null), + ), $arguments); + } + + public function testSignature2() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature2')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, true, null, true), + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null, true), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null, true), + ), $arguments); + } + + public function testSignature3() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature3')); + + $this->assertEquals(array( + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, false, null), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, false, null), + ), $arguments); + } + + public function testSignature4() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature4')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, true, 'default'), + new ArgumentMetadata('bar', null, false, true, 500), + new ArgumentMetadata('baz', null, false, true, array()), + ), $arguments); + } + + public function testSignature5() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature5')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'array', false, true, null, true), + new ArgumentMetadata('bar', null, false, false, null), + ), $arguments); + } + + /** + * @requires PHP 5.6 + */ + public function testVariadicSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new VariadicController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, false, null), + new ArgumentMetadata('bar', null, true, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.0 + */ + public function testBasicTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new BasicTypesController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null), + new ArgumentMetadata('bar', 'int', false, false, null), + new ArgumentMetadata('baz', 'float', false, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.1 + */ + public function testNullableTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new NullableController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null, true), + new ArgumentMetadata('bar', \stdClass::class, false, false, null, true), + new ArgumentMetadata('baz', 'string', false, true, 'value', true), + new ArgumentMetadata('mandatory', null, false, false, null, true), + ), $arguments); + } + + private function signature1(ArgumentMetadataFactoryTest $foo, array $bar, callable $baz) + { + } + + private function signature2(ArgumentMetadataFactoryTest $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) + { + } + + private function signature3(FakeClassThatDoesNotExist $bar, ImportedAndFake $baz) + { + } + + private function signature4($foo = 'default', $bar = 500, $baz = array()) + { + } + + private function signature5(array $foo = null, $bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php new file mode 100644 index 0000000000..05351445e0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +class ArgumentMetadataTest extends TestCase +{ + public function testWithBcLayerWithDefault() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value'); + + $this->assertFalse($argument->isNullable()); + } + + public function testDefaultValueAvailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true); + + $this->assertTrue($argument->isNullable()); + $this->assertTrue($argument->hasDefaultValue()); + $this->assertSame('default value', $argument->getDefaultValue()); + } + + /** + * @expectedException \LogicException + */ + public function testDefaultValueUnavailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, false, null, false); + + $this->assertFalse($argument->isNullable()); + $this->assertFalse($argument->hasDefaultValue()); + $argument->getDefaultValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log new file mode 100644 index 0000000000..88b6840eae --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log @@ -0,0 +1,4 @@ +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Container\ContainerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias. +Some custom logging message +With ending : diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php new file mode 100644 index 0000000000..4fb36afda2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php @@ -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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ConfigDataCollectorTest extends TestCase +{ + public function testCollect() + { + $kernel = new KernelForTest('test', true); + $c = new ConfigDataCollector(); + $c->setKernel($kernel); + $c->collect(new Request(), new Response()); + + $this->assertSame('test', $c->getEnv()); + $this->assertTrue($c->isDebug()); + $this->assertSame('config', $c->getName()); + $this->assertSame('testkernel', $c->getAppName()); + $this->assertRegExp('~^'.preg_quote($c->getPhpVersion(), '~').'~', PHP_VERSION); + $this->assertRegExp('~'.preg_quote((string) $c->getPhpVersionExtra(), '~').'$~', PHP_VERSION); + $this->assertSame(PHP_INT_SIZE * 8, $c->getPhpArchitecture()); + $this->assertSame(class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', $c->getPhpIntlLocale()); + $this->assertSame(date_default_timezone_get(), $c->getPhpTimezone()); + $this->assertSame(Kernel::VERSION, $c->getSymfonyVersion()); + $this->assertNull($c->getToken()); + $this->assertSame(extension_loaded('xdebug'), $c->hasXDebug()); + $this->assertSame(extension_loaded('Zend OPcache') && ini_get('opcache.enable'), $c->hasZendOpcache()); + $this->assertSame(extension_loaded('apcu') && ini_get('apc.enabled'), $c->hasApcu()); + } +} + +class KernelForTest extends Kernel +{ + public function getName() + { + return 'testkernel'; + } + + public function registerBundles() + { + } + + public function getBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php new file mode 100644 index 0000000000..54fd39e0d8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php @@ -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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector\CloneVarDataCollector; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +class DataCollectorTest extends TestCase +{ + public function testCloneVarStringWithScheme() + { + $c = new CloneVarDataCollector('scheme://foo'); + $c->collect(new Request(), new Response()); + $cloner = new VarCloner(); + + $this->assertEquals($cloner->cloneVar('scheme://foo'), $c->getData()); + } + + public function testCloneVarExistingFilePath() + { + $c = new CloneVarDataCollector(array($filePath = tempnam(sys_get_temp_dir(), 'clone_var_data_collector_'))); + $c->collect(new Request(), new Response()); + + $this->assertSame($filePath, $c->getData()[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php new file mode 100644 index 0000000000..e642e3c337 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class DumpDataCollectorTest extends TestCase +{ + public function testDump() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $this->assertSame('dump', $collector->getName()); + + $collector->dump($data); + $line = __LINE__ - 1; + $this->assertSame(1, $collector->getDumpsCount()); + + $dump = $collector->getDumps('html'); + $this->assertTrue(isset($dump[0]['data'])); + $dump[0]['data'] = preg_replace('/^.*?<pre/', '<pre', $dump[0]['data']); + $dump[0]['data'] = preg_replace('/sf-dump-\d+/', 'sf-dump', $dump[0]['data']); + + $xDump = array( + array( + 'data' => "<pre class=sf-dump id=sf-dump data-indent-pad=\" \"><span class=sf-dump-num>123</span>\n</pre><script>Sfdump(\"sf-dump\")</script>\n", + 'name' => 'DumpDataCollectorTest.php', + 'file' => __FILE__, + 'line' => $line, + 'fileExcerpt' => false, + ), + ); + $this->assertEquals($xDump, $dump); + + $this->assertStringMatchesFormat('a:3:{i:0;a:5:{s:4:"data";%c:39:"Symfony\Component\VarDumper\Cloner\Data":%a', $collector->serialize()); + $this->assertSame(0, $collector->getDumpsCount()); + $this->assertSame('a:2:{i:0;b:0;i:1;s:5:"UTF-8";}', $collector->serialize()); + } + + public function testCollectDefault() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->collect(new Request(), new Response()); + $output = ob_get_clean(); + + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n123\n", $output); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testCollectHtml() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(null, 'test://%f:%l'); + + $collector->dump($data); + $line = __LINE__ - 1; + $file = __FILE__; + $xOutput = <<<EOTXT +<pre class=sf-dump id=sf-dump data-indent-pad=" "><a href="test://{$file}:{$line}" title="{$file}"><span class=sf-dump-meta>DumpDataCollectorTest.php</span></a> on line <span class=sf-dump-meta>{$line}</span>: +<span class=sf-dump-num>123</span> +</pre> +EOTXT; + + ob_start(); + $response = new Response(); + $response->headers->set('Content-Type', 'text/html'); + $collector->collect(new Request(), $response); + $output = ob_get_clean(); + $output = preg_replace('#<(script|style).*?</\1>#s', '', $output); + $output = preg_replace('/sf-dump-\d+/', 'sf-dump', $output); + + $this->assertSame($xOutput, trim($output)); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testFlush() + { + $data = new Data(array(array(456))); + $collector = new DumpDataCollector(); + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->__destruct(); + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php new file mode 100644 index 0000000000..afad9f58af --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ExceptionDataCollectorTest extends TestCase +{ + public function testCollect() + { + $e = new \Exception('foo', 500); + $c = new ExceptionDataCollector(); + $flattened = FlattenException::create($e); + $trace = $flattened->getTrace(); + + $this->assertFalse($c->hasException()); + + $c->collect(new Request(), new Response(), $e); + + $this->assertTrue($c->hasException()); + $this->assertEquals($flattened, $c->getException()); + $this->assertSame('foo', $c->getMessage()); + $this->assertSame(500, $c->getCode()); + $this->assertSame('exception', $c->getName()); + $this->assertSame($trace, $c->getTrace()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php new file mode 100644 index 0000000000..62bf2c00c7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -0,0 +1,126 @@ +<?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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; + +class LoggerDataCollectorTest extends TestCase +{ + public function testCollectWithUnexpectedFormat() + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue('foo')); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue(array())); + + $c = new LoggerDataCollector($logger, __DIR__.'/'); + $c->lateCollect(); + $compilerLogs = $c->getCompilerLogs()->getValue('message'); + + $this->assertSame(array( + array('message' => 'Removed service "Psr\Container\ContainerInterface"; reason: private alias.'), + array('message' => 'Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias.'), + ), $compilerLogs['Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass']); + + $this->assertSame(array( + array('message' => 'Some custom logging message'), + array('message' => 'With ending :'), + ), $compilerLogs['Unknown Compiler Pass']); + } + + /** + * @dataProvider getCollectTestData + */ + public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount, $expectedScreamCount, $expectedPriorities = null) + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb)); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs)); + + $c = new LoggerDataCollector($logger); + $c->lateCollect(); + + $this->assertEquals('logger', $c->getName()); + $this->assertEquals($nb, $c->countErrors()); + + $logs = array_map(function ($v) { + if (isset($v['context']['exception'])) { + $e = &$v['context']['exception']; + $e = isset($e["\0*\0message"]) ? array($e["\0*\0message"], $e["\0*\0severity"]) : array($e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]); + } + + return $v; + }, $c->getLogs()->getValue(true)); + $this->assertEquals($expectedLogs, $logs); + $this->assertEquals($expectedDeprecationCount, $c->countDeprecations()); + $this->assertEquals($expectedScreamCount, $c->countScreams()); + + if (isset($expectedPriorities)) { + $this->assertSame($expectedPriorities, $c->getPriorities()->getValue(true)); + } + } + + public function getCollectTestData() + { + yield 'simple log' => array( + 1, + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + yield 'log with a context' => array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + if (!class_exists(SilencedErrorContext::class)) { + return; + } + + yield 'logs with some deprecations' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo2', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => array('deprecated', E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + array('message' => 'foo2', 'context' => array('exception' => array('deprecated', E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + ), + 2, + 0, + array(100 => array('count' => 3, 'name' => 'DEBUG')), + ); + + yield 'logs with some silent errors' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => array(E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true), + ), + 0, + 1, + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php new file mode 100644 index 0000000000..ab78e9e8e1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php @@ -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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MemoryDataCollectorTest extends TestCase +{ + public function testCollect() + { + $collector = new MemoryDataCollector(); + $collector->collect(new Request(), new Response()); + + $this->assertInternalType('integer', $collector->getMemory()); + $this->assertInternalType('integer', $collector->getMemoryLimit()); + $this->assertSame('memory', $collector->getName()); + } + + /** @dataProvider getBytesConversionTestData */ + public function testBytesConversion($limit, $bytes) + { + $collector = new MemoryDataCollector(); + $method = new \ReflectionMethod($collector, 'convertToBytes'); + $method->setAccessible(true); + $this->assertEquals($bytes, $method->invoke($collector, $limit)); + } + + public function getBytesConversionTestData() + { + return array( + array('2k', 2048), + array('2 k', 2048), + array('8m', 8 * 1024 * 1024), + array('+2 k', 2048), + array('+2???k', 2048), + array('0x10', 16), + array('0xf', 15), + array('010', 8), + array('+0x10 k', 16 * 1024), + array('1g', 1024 * 1024 * 1024), + array('1G', 1024 * 1024 * 1024), + array('-1', -1), + array('0', 0), + array('2mk', 2048), // the unit must be the last char, so in this case 'k', not 'm' + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php new file mode 100644 index 0000000000..93767b9684 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class RequestDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new RequestDataCollector(); + + $c->collect($request = $this->createRequest(), $this->createResponse()); + $c->lateCollect(); + + $attributes = $c->getRequestAttributes(); + + $this->assertSame('request', $c->getName()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestHeaders()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestServer()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestCookies()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $attributes); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery()); + $this->assertInstanceOf(ParameterBag::class, $c->getResponseCookies()); + $this->assertSame('html', $c->getFormat()); + $this->assertEquals('foobar', $c->getRoute()); + $this->assertEquals(array('name' => 'foo'), $c->getRouteParams()); + $this->assertSame(array(), $c->getSessionAttributes()); + $this->assertSame('en', $c->getLocale()); + $this->assertContains(__FILE__, $attributes->get('resource')); + $this->assertSame('stdClass', $attributes->get('object')->getType()); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getResponseHeaders()); + $this->assertSame('OK', $c->getStatusText()); + $this->assertSame(200, $c->getStatusCode()); + $this->assertSame('application/json', $c->getContentType()); + } + + public function testCollectWithoutRouteParams() + { + $request = $this->createRequest(array()); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + $c->lateCollect(); + + $this->assertEquals(array(), $c->getRouteParams()); + } + + public function testKernelResponseDoesNotStartSession() + { + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); + $request = new Request(); + $session = new Session(new MockArraySessionStorage()); + $request->setSession($session); + $response = new Response(); + + $c = new RequestDataCollector(); + $c->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response)); + + $this->assertFalse($session->isStarted()); + } + + /** + * @dataProvider provideControllerCallables + */ + public function testControllerInspection($name, $callable, $expected) + { + $c = new RequestDataCollector(); + $request = $this->createRequest(); + $response = $this->createResponse(); + $this->injectController($c, $callable, $request); + $c->collect($request, $response); + $c->lateCollect(); + + $this->assertSame($expected, $c->getController()->getValue(true), sprintf('Testing: %s', $name)); + } + + public function provideControllerCallables() + { + // make sure we always match the line number + $r1 = new \ReflectionMethod($this, 'testControllerInspection'); + $r2 = new \ReflectionMethod($this, 'staticControllerMethod'); + $r3 = new \ReflectionClass($this); + + // test name, callable, expected + return array( + array( + '"Regular" callable', + array($this, 'testControllerInspection'), + array( + 'class' => __NAMESPACE__.'\RequestDataCollectorTest', + 'method' => 'testControllerInspection', + 'file' => __FILE__, + 'line' => $r1->getStartLine(), + ), + ), + + array( + 'Closure', + function () { return 'foo'; }, + array( + 'class' => __NAMESPACE__.'\{closure}', + 'method' => null, + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ), + ), + + array( + 'Static callback as string', + __NAMESPACE__.'\RequestDataCollectorTest::staticControllerMethod', + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with instance', + array($this, 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with class name', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Callable with instance depending on __call()', + array($this, 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Callable with class name depending on __callStatic()', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Invokable controller', + $this, + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => null, + 'file' => __FILE__, + 'line' => $r3->getStartLine(), + ), + ), + ); + } + + public function testItIgnoresInvalidCallables() + { + $request = $this->createRequestWithSession(); + $response = new RedirectResponse('/'); + + $c = new RequestDataCollector(); + $c->collect($request, $response); + + $this->assertSame('n/a', $c->getController()); + } + + protected function createRequest($routeParams = array('name' => 'foo')) + { + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('foo', 'bar'); + $request->attributes->set('_route', 'foobar'); + $request->attributes->set('_route_params', $routeParams); + $request->attributes->set('resource', fopen(__FILE__, 'r')); + $request->attributes->set('object', new \stdClass()); + + return $request; + } + + private function createRequestWithSession() + { + $request = $this->createRequest(); + $request->attributes->set('_controller', 'Foo::bar'); + $request->setSession(new Session(new MockArraySessionStorage())); + $request->getSession()->start(); + + return $request; + } + + protected function createResponse() + { + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'application/json'); + $response->headers->set('X-Foo-Bar', null); + $response->headers->setCookie(new Cookie('foo', 'bar', 1, '/foo', 'localhost', true, true)); + $response->headers->setCookie(new Cookie('bar', 'foo', new \DateTime('@946684800'))); + $response->headers->setCookie(new Cookie('bazz', 'foo', '2000-12-12')); + + return $response; + } + + /** + * Inject the given controller callable into the data collector. + */ + protected function injectController($collector, $controller, $request) + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $httpKernel = new HttpKernel(new EventDispatcher(), $resolver, null, $this->getMockBuilder(ArgumentResolverInterface::class)->getMock()); + $event = new FilterControllerEvent($httpKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + $collector->onKernelController($event); + } + + /** + * Dummy method used as controller callable. + */ + public static function staticControllerMethod() + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public function __call($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public static function __callStatic($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + public function __invoke() + { + throw new \LogicException('Unexpected method call'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php new file mode 100644 index 0000000000..8048cc37a5 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php @@ -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\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class TimeDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new TimeDataCollector(); + + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + + $this->assertEquals(0, $c->getStartTime()); + + $request->server->set('REQUEST_TIME_FLOAT', 2); + + $c->collect($request, new Response()); + + $this->assertEquals(2000, $c->getStartTime()); + + $request = new Request(); + $c->collect($request, new Response()); + $this->assertEquals(0, $c->getStartTime()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel->expects($this->once())->method('getStartTime')->will($this->returnValue(123456)); + + $c = new TimeDataCollector($kernel); + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + $this->assertEquals(123456000, $c->getStartTime()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php new file mode 100644 index 0000000000..5fe92d60e0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector\Util; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +/** + * @group legacy + */ +class ValueExporterTest extends TestCase +{ + /** + * @var ValueExporter + */ + private $valueExporter; + + protected function setUp() + { + $this->valueExporter = new ValueExporter(); + } + + public function testDateTime() + { + $dateTime = new \DateTime('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTime) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testDateTimeImmutable() + { + $dateTime = new \DateTimeImmutable('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTimeImmutable) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testIncompleteClass() + { + $foo = new \__PHP_Incomplete_Class(); + $array = new \ArrayObject($foo); + $array['__PHP_Incomplete_Class_Name'] = 'AppBundle/Foo'; + $this->assertSame('__PHP_Incomplete_Class(AppBundle/Foo)', $this->valueExporter->exportValue($foo)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php new file mode 100644 index 0000000000..d616098a54 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php @@ -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\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; + +class FileLinkFormatterTest extends TestCase +{ + public function testWhenNoFileLinkFormatAndNoRequest() + { + $sut = new FileLinkFormatter(); + + $this->assertFalse($sut->format('/kernel/root/src/my/very/best/file.php', 3)); + } + + public function testWhenFileLinkFormatAndNoRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', new RequestStack()); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $baseDir = __DIR__; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenNoFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $request->server->set('SERVER_NAME', 'www.example.org'); + $request->server->set('SERVER_PORT', 80); + $request->server->set('SCRIPT_NAME', '/app.php'); + $request->server->set('SCRIPT_FILENAME', '/web/app.php'); + $request->server->set('REQUEST_URI', '/app.php/example'); + + $sut = new FileLinkFormatter(null, $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame('http://www.example.org/app.php/_profiler/open?file=file.php&line=3#line3', $sut->format($file, 3)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 0000000000..aaa82d524c --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,121 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends TestCase +{ + public function testStopwatchSections() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + $events = $stopwatch->getSectionEvents($response->headers->get('X-Debug-Token')); + $this->assertEquals(array( + '__section__', + 'kernel.request', + 'kernel.controller', + 'kernel.controller_arguments', + 'controller', + 'kernel.response', + 'kernel.terminate', + ), array_keys($events)); + } + + public function testStopwatchCheckControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testStopwatchStopControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted', 'stop', 'stopSection')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $stopwatch->expects($this->once()) + ->method('stop'); + $stopwatch->expects($this->once()) + ->method('stopSection'); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testAddListenerNested() + { + $called1 = false; + $called2 = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('my-event', function () use ($dispatcher, &$called1, &$called2) { + $called1 = true; + $dispatcher->addListener('my-event', function () use (&$called2) { + $called2 = true; + }); + }); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called1); + $this->assertFalse($called2); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called2); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function () use ($eventDispatcher, &$listener1) { + $eventDispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } + + protected function getHttpKernel($dispatcher, $controller) + { + $controllerResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface')->getMock(); + $controllerResolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $argumentResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface')->getMock(); + $argumentResolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + + return new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php new file mode 100644 index 0000000000..a2fb6afca4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php @@ -0,0 +1,99 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; + +class AddAnnotatedClassesToCachePassTest extends TestCase +{ + public function testExpandClasses() + { + $r = new \ReflectionClass(AddAnnotatedClassesToCachePass::class); + $pass = $r->newInstanceWithoutConstructor(); + $r = new \ReflectionMethod(AddAnnotatedClassesToCachePass::class, 'expandClasses'); + $r->setAccessible(true); + $expand = $r->getClosure($pass); + + $this->assertSame('Foo', $expand(array('Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('Foo'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar\\Acme'))[0]); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Bar\\Acme', $expand(array('Foo\\'), array('\\Foo\\Bar\\Acme'))[0]); + $this->assertEmpty($expand(array('Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo\\Bar', $expand(array('**\\Foo\\'), array('\\Acme\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo\\Bar'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Acme\\Foo'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo', $expand(array('**\\Foo'), array('\\Acme\\Foo'))[0]); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\Foo\\AcmeBundle'))); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\FooBar\\AcmeBundle'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + + $this->assertSame('Acme\\Bar', $expand(array('*\\Bar'), array('\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Bar'))); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Bar'), array('\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\*'), array('\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + + $this->assertSame(array('Foo\\Bar'), $expand(array('Foo\\*'), array('Foo\\Bar', 'Foo\\BarTest'))); + $this->assertSame(array('Foo\\Bar', 'Foo\\BarTest'), $expand(array('Foo\\*', 'Foo\\*Test'), array('Foo\\Bar', 'Foo\\BarTest'))); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\Bar'), array())[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php new file mode 100644 index 0000000000..df8977de0b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php @@ -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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; + +class ControllerArgumentValueResolverPassTest extends TestCase +{ + public function testServicesAreOrderedAccordingToPriority() + { + $services = array( + 'n3' => array(array()), + 'n1' => array(array('priority' => 200)), + 'n2' => array(array('priority' => 100)), + ); + + $expected = array( + new Reference('n1'), + new Reference('n2'), + new Reference('n3'), + ); + + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + foreach ($services as $id => list($tag)) { + $container->register($id)->addTag('controller.argument_value_resolver', $tag); + } + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals($expected, $definition->getArgument(1)->getValues()); + } + + public function testReturningEmptyArrayWhenNoService() + { + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals(array(), $definition->getArgument(1)->getValues()); + } + + public function testNoArgumentResolver() + { + $container = new ContainerBuilder(); + + (new ControllerArgumentValueResolverPass())->process($container); + + $this->assertFalse($container->hasDefinition('argument_resolver')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php new file mode 100644 index 0000000000..d28c6eca57 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +class FragmentRendererPassTest extends TestCase +{ + /** + * Tests that content rendering not implementing FragmentRendererInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testContentRendererWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + } + + public function testValidContentRenderer() + { + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $renderer = new Definition('', array(null)); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'getReflectionClass'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->onConsecutiveCalls($renderer, $definition)); + + $builder->expects($this->atLeastOnce()) + ->method('getReflectionClass') + ->with('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') + ->will($this->returnValue(new \ReflectionClass('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService'))); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + + $this->assertInstanceOf(Reference::class, $renderer->getArgument(0)); + } +} + +class RendererService implements FragmentRendererInterface +{ + public function render($uri, Request $request = null, array $options = array()) + { + } + + public function getName() + { + return 'test'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php new file mode 100644 index 0000000000..0406345d96 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php @@ -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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LazyLoadingFragmentHandlerTest extends TestCase +{ + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler::addRendererService() method is deprecated since version 3.3 and will be removed in 4.0. + */ + public function testRenderWithLegacyMapping() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + $handler->addRendererService('foo', 'foo'); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } + + public function testRender() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock(); + $container->expects($this->once())->method('has')->with('foo')->willReturn(true); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php new file mode 100644 index 0000000000..81fc8b455d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class MergeExtensionConfigurationPassTest extends TestCase +{ + public function testAutoloadMainExtension() + { + $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock(); + $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock(); + + $container->expects($this->at(0)) + ->method('getExtensionConfig') + ->with('loaded') + ->will($this->returnValue(array(array()))); + $container->expects($this->at(1)) + ->method('getExtensionConfig') + ->with('notloaded') + ->will($this->returnValue(array())); + $container->expects($this->once()) + ->method('loadFromExtension') + ->with('notloaded', array()); + + $container->expects($this->any()) + ->method('getParameterBag') + ->will($this->returnValue($params)); + $params->expects($this->any()) + ->method('all') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getAliases') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $configPass->process($container); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php new file mode 100644 index 0000000000..0542698d69 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -0,0 +1,329 @@ +<?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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; + +class RegisterControllerArgumentLocatorsPassTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Class "Symfony\Component\HttpKernel\Tests\DependencyInjection\NotFound" used for service "foo" cannot be found. + */ + public function testInvalidClass() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NotFound::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "action" attribute on tag "controller.service_arguments" {"argument":"bar"} for service "foo". + */ + public function testNoAction() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "argument" attribute on tag "controller.service_arguments" {"action":"fooAction"} for service "foo". + */ + public function testNoArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "id" attribute on tag "controller.service_arguments" {"action":"fooAction","argument":"bar"} for service "foo". + */ + public function testNoService() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "action" attribute on tag "controller.service_arguments" for service "foo": no public "barAction()" method found on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidMethod() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'barAction', 'argument' => 'bar', 'id' => 'bar_service')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "controller.service_arguments" tag for service "foo": method "fooAction()" has no "baz" argument on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'baz', 'id' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testAllActions() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertEquals(array('foo:fooAction'), array_keys($locator)); + $this->assertInstanceof(ServiceClosureArgument::class, $locator['foo:fooAction']); + + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $this->assertSame(ServiceLocator::class, $locator->getClass()); + $this->assertFalse($locator->isPublic()); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference(ControllerDummy::class, ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testExplicitArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'bar')) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'baz')) // should be ignored, the first wins + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testOptionalArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => '?bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testSkipSetContainer() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ContainerAwareRegisterTestController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:fooAction'), array_keys($locator)); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClass". Did you forget to add a use statement? + */ + public function testExceptionOnNonExistentTypeHint() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassDifferentNamespaceController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Acme\NonExistentClass". + */ + public function testExceptionOnNonExistentTypeHintDifferentNamespace() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassDifferentNamespaceController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testNoExceptionOnNonExistentTypeHintOptionalArg() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassOptionalController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:barAction', 'foo:fooAction'), array_keys($locator)); + } + + public function testArgumentWithNoTypeHintIsOk() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ArgumentWithoutTypeController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertEmpty(array_keys($locator)); + } +} + +class RegisterTestController +{ + public function __construct(ControllerDummy $bar) + { + } + + public function fooAction(ControllerDummy $bar) + { + } + + protected function barAction(ControllerDummy $bar) + { + } +} + +class ContainerAwareRegisterTestController implements ContainerAwareInterface +{ + use ContainerAwareTrait; + + public function fooAction(ControllerDummy $bar) + { + } +} + +class ControllerDummy +{ +} + +class NonExistentClassController +{ + public function fooAction(NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassDifferentNamespaceController +{ + public function fooAction(\Acme\NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassOptionalController +{ + public function fooAction(NonExistentClass $nonExistent = null) + { + } + + public function barAction(NonExistentClass $nonExistent = null, $bar) + { + } +} + +class ArgumentWithoutTypeController +{ + public function fooAction($someArg) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php new file mode 100644 index 0000000000..9cac968185 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php @@ -0,0 +1,148 @@ +<?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\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; + +class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('stdClass', 'stdClass'); + $container->register(parent::class, 'stdClass'); + $container->register('c1', RemoveTestController1::class)->addTag('controller.service_arguments'); + $container->register('c2', RemoveTestController2::class)->addTag('controller.service_arguments') + ->addMethodCall('setTestCase', array(new Reference('c1'))); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertCount(2, $container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new ResolveInvalidReferencesPass())->process($container); + + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertSame(array(), $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertSame(array('c1:fooAction'), array_keys($controllers)); + $this->assertSame(array('bar'), array_keys($container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0))); + + $expectedLog = array( + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing service-argument resolver for controller "c2:fooAction": no corresponding services exist for the referenced types.', + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing method "setTestCase" of service "c2" from controller candidates: the method is called at instantiation, thus cannot be an action.', + ); + + $this->assertSame($expectedLog, $container->getCompiler()->getLog()); + } + + public function testSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(RegisterTestController::class, RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + RegisterTestController::class.':fooAction', + RegisterTestController::class.'::fooAction', + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } + + public function testInvoke() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('invokable', InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $this->assertEquals( + array('invokable:__invoke', 'invokable'), + array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0)) + ); + } + + public function testInvokeSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(InvokableRegisterTestController::class, InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + InvokableRegisterTestController::class.':__invoke', + InvokableRegisterTestController::class.'::__invoke', + InvokableRegisterTestController::class, + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } +} + +class RemoveTestController1 +{ + public function fooAction(\stdClass $bar, ClassNotInContainer $baz) + { + } +} + +class RemoveTestController2 +{ + public function setTestCase(TestCase $test) + { + } + + public function fooAction(ClassNotInContainer $bar) + { + } +} + +class InvokableRegisterTestController +{ + public function __invoke(\stdClass $bar) + { + } +} + +class ClassNotInContainer +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php new file mode 100644 index 0000000000..7242579301 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php @@ -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\Tests\Event; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Tests\TestHttpKernel; + +class GetResponseForExceptionEventTest extends TestCase +{ + public function testAllowSuccessfulResponseIsFalseByDefault() + { + $event = new GetResponseForExceptionEvent(new TestHttpKernel(), new Request(), 1, new \Exception()); + + $this->assertFalse($event->isAllowingCustomResponseCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php new file mode 100644 index 0000000000..f4878f626f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php @@ -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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Test AddRequestFormatsListener class. + * + * @author Gildas Quemener <gildas.quemener@gmail.com> + */ +class AddRequestFormatsListenerTest extends TestCase +{ + /** + * @var AddRequestFormatsListener + */ + private $listener; + + protected function setUp() + { + $this->listener = new AddRequestFormatsListener(array('csv' => array('text/csv', 'text/plain'))); + } + + protected function tearDown() + { + $this->listener = null; + } + + public function testIsAnEventSubscriber() + { + $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventSubscriberInterface', $this->listener); + } + + public function testRegisteredEvent() + { + $this->assertEquals( + array(KernelEvents::REQUEST => array('onKernelRequest', 1)), + AddRequestFormatsListener::getSubscribedEvents() + ); + } + + public function testSetAdditionalFormats() + { + $request = $this->getRequestMock(); + $event = $this->getGetResponseEventMock($request); + + $request->expects($this->once()) + ->method('setFormat') + ->with('csv', array('text/csv', 'text/plain')); + + $this->listener->onKernelRequest($event); + } + + protected function getRequestMock() + { + return $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + } + + protected function getGetResponseEventMock(Request $request) + { + $event = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)); + + return $event; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php new file mode 100644 index 0000000000..d1349906bb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -0,0 +1,135 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * DebugHandlersListenerTest. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class DebugHandlersListenerTest extends TestCase +{ + public function testConfigure() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $userHandler = function () {}; + $listener = new DebugHandlersListener($userHandler, $logger); + $xHandler = new ExceptionHandler(); + $eHandler = new ErrorHandler(); + $eHandler->setExceptionHandler(array($xHandler, 'handle')); + + $exception = null; + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure(); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertSame($userHandler, $xHandler->setHandler('var_dump')); + + $loggers = $eHandler->setLoggers(array()); + + $this->assertArrayHasKey(E_DEPRECATED, $loggers); + $this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]); + } + + public function testConfigureForHttpKernelWithNoTerminateWithException() + { + $listener = new DebugHandlersListener(null); + $eHandler = new ErrorHandler(); + $event = new KernelEvent( + $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), + Request::create('/'), + HttpKernelInterface::MASTER_REQUEST + ); + + $exception = null; + $h = set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure($event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertNull($h); + } + + public function testConsoleEvent() + { + $dispatcher = new EventDispatcher(); + $listener = new DebugHandlersListener(null); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet())); + $command = new Command(__FUNCTION__); + $command->setApplication($app); + $event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput()); + + $dispatcher->addSubscriber($listener); + + $xListeners = array( + KernelEvents::REQUEST => array(array($listener, 'configure')), + ConsoleEvents::COMMAND => array(array($listener, 'configure')), + ); + $this->assertSame($xListeners, $dispatcher->getListeners()); + + $exception = null; + $eHandler = new ErrorHandler(); + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $xHandler = $eHandler->setExceptionHandler('var_dump'); + $this->assertInstanceOf('Closure', $xHandler); + + $app->expects($this->once()) + ->method('renderException'); + + $xHandler(new \Exception()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php new file mode 100644 index 0000000000..509f443087 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php @@ -0,0 +1,81 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\HttpKernel\EventListener\DumpListener; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * DumpListenerTest. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class DumpListenerTest extends TestCase +{ + public function testSubscribedEvents() + { + $this->assertSame( + array(ConsoleEvents::COMMAND => array('configure', 1024)), + DumpListener::getSubscribedEvents() + ); + } + + public function testConfigure() + { + $prevDumper = VarDumper::setHandler('var_dump'); + VarDumper::setHandler($prevDumper); + + $cloner = new MockCloner(); + $dumper = new MockDumper(); + + ob_start(); + $exception = null; + $listener = new DumpListener($cloner, $dumper); + + try { + $listener->configure(); + + VarDumper::dump('foo'); + VarDumper::dump('bar'); + + $this->assertSame('+foo-+bar-', ob_get_clean()); + } catch (\Exception $exception) { + } + + VarDumper::setHandler($prevDumper); + + if (null !== $exception) { + throw $exception; + } + } +} + +class MockCloner implements ClonerInterface +{ + public function cloneVar($var) + { + return new Data(array(array($var.'-'))); + } +} + +class MockDumper implements DataDumperInterface +{ + public function dump(Data $data) + { + echo '+'.$data->getValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php new file mode 100644 index 0000000000..f5501a3f59 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php @@ -0,0 +1,149 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Logger; + +/** + * ExceptionListenerTest. + * + * @author Robert Schönthal <seroscho@googlemail.com> + * + * @group time-sensitive + */ +class ExceptionListenerTest extends TestCase +{ + public function testConstruct() + { + $logger = new TestLogger(); + $l = new ExceptionListener('foo', $logger); + + $_logger = new \ReflectionProperty(get_class($l), 'logger'); + $_logger->setAccessible(true); + $_controller = new \ReflectionProperty(get_class($l), 'controller'); + $_controller->setAccessible(true); + + $this->assertSame($logger, $_logger->getValue($l)); + $this->assertSame('foo', $_controller->getValue($l)); + } + + /** + * @dataProvider provider + */ + public function testHandleWithoutLogger($event, $event2) + { + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $l = new ExceptionListener('foo'); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + } + + /** + * @dataProvider provider + */ + public function testHandleWithLogger($event, $event2) + { + $logger = new TestLogger(); + + $l = new ExceptionListener('foo', $logger); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(3, $logger->countErrors()); + $this->assertCount(3, $logger->getLogs('critical')); + } + + public function provider() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return array(array(null, null)); + } + + $request = new Request(); + $exception = new \Exception('foo'); + $event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception); + $event2 = new GetResponseForExceptionEvent(new TestKernelThatThrowsException(), $request, 'foo', $exception); + + return array( + array($event, $event2), + ); + } + + public function testSubRequestFormat() + { + $listener = new ExceptionListener('foo', $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { + return new Response($request->getRequestFormat()); + })); + + $request = Request::create('/'); + $request->setRequestFormat('xml'); + + $event = new GetResponseForExceptionEvent($kernel, $request, 'foo', new \Exception('foo')); + $listener->onKernelException($event); + + $response = $event->getResponse(); + $this->assertEquals('xml', $response->getContent()); + } +} + +class TestLogger extends Logger implements DebugLoggerInterface +{ + public function countErrors() + { + return count($this->logs['critical']); + } +} + +class TestKernel implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + return new Response('foo'); + } +} + +class TestKernelThatThrowsException implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + throw new \RuntimeException('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php new file mode 100644 index 0000000000..464b2ab462 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php @@ -0,0 +1,122 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\UriSigner; + +class FragmentListenerTest extends TestCase +{ + public function testOnlyTriggeredOnFragmentRoute() + { + $request = Request::create('http://example.com/foo?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + $this->assertTrue($request->query->has('_path')); + } + + public function testOnlyTriggeredIfControllerWasNotDefinedYet() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + $request->attributes->set('_controller', 'bar'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonSafeMethods() + { + $request = Request::create('http://example.com/_fragment', 'POST'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithWrongSignature() + { + $request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + public function testWithSignature() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'bar', '_controller' => 'foo'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerDefined() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerNotDefined() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + private function createGetResponseEvent(Request $request, $requestType = HttpKernelInterface::MASTER_REQUEST) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, $requestType); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php new file mode 100644 index 0000000000..2ce32819e1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php @@ -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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +class LocaleListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + public function testDefaultLocaleWithoutSession() + { + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request = Request::create('/')); + + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testLocaleFromRequestAttribute() + { + $request = Request::create('/'); + session_name('foo'); + $request->cookies->set('foo', 'value'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('es', $request->getLocale()); + } + + public function testLocaleSetForRoutingContext() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $request = Request::create('/'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelRequest($this->getEvent($request)); + } + + public function testRouterResetWithParentRequestOnKernelFinishRequest() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $parentRequest = Request::create('/'); + $parentRequest->setLocale('es'); + + $this->requestStack->expects($this->once())->method('getParentRequest')->will($this->returnValue($parentRequest)); + + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FinishRequestEvent')->disableOriginalConstructor()->getMock(); + + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelFinishRequest($event); + } + + public function testRequestLocaleIsNotOverridden() + { + $request = Request::create('/'); + $request->setLocale('de'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + private function getEvent(Request $request) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php new file mode 100644 index 0000000000..751aee8695 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php @@ -0,0 +1,71 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Kernel; + +class ProfilerListenerTest extends TestCase +{ + /** + * Test a master and sub request with an exception and `onlyException` profiler option enabled. + */ + public function testKernelTerminate() + { + $profile = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profile') + ->disableOriginalConstructor() + ->getMock(); + + $profiler = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $profiler->expects($this->once()) + ->method('collect') + ->will($this->returnValue($profile)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $masterRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $subRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response') + ->disableOriginalConstructor() + ->getMock(); + + $requestStack = new RequestStack(); + $requestStack->push($masterRequest); + + $onlyException = true; + $listener = new ProfilerListener($profiler, $requestStack, null, $onlyException); + + // master request + $listener->onKernelResponse(new FilterResponseEvent($kernel, $masterRequest, Kernel::MASTER_REQUEST, $response)); + + // sub request + $listener->onKernelException(new GetResponseForExceptionEvent($kernel, $subRequest, Kernel::SUB_REQUEST, new HttpException(404))); + $listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, Kernel::SUB_REQUEST, $response)); + + $listener->onKernelTerminate(new PostResponseEvent($kernel, $masterRequest, $response)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php new file mode 100644 index 0000000000..12a31eb3ee --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php @@ -0,0 +1,95 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ResponseListenerTest extends TestCase +{ + private $dispatcher; + + private $kernel; + + protected function setUp() + { + $this->dispatcher = new EventDispatcher(); + $listener = new ResponseListener('UTF-8'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + + $this->kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->kernel = null; + } + + public function testFilterDoesNothingForSubRequests() + { + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('content-type')); + } + + public function testFilterSetsNonDefaultCharsetIfNotOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } + + public function testFilterDoesNothingIfCharsetIsOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $response->setCharset('ISO-8859-1'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-1', $response->getCharset()); + } + + public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentType() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('application/json'); + + $event = new FilterResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php new file mode 100644 index 0000000000..a40e57998d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php @@ -0,0 +1,188 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Routing\RequestContext; + +class RouterListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + /** + * @dataProvider getPortData + */ + public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHttpPort, $expectedHttpsPort) + { + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface') + ->disableOriginalConstructor() + ->getMock(); + $context = new RequestContext(); + $context->setHttpPort($defaultHttpPort); + $context->setHttpsPort($defaultHttpsPort); + $urlMatcher->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + + $listener = new RouterListener($urlMatcher, $this->requestStack); + $event = $this->createGetResponseEventForUri($uri); + $listener->onKernelRequest($event); + + $this->assertEquals($expectedHttpPort, $context->getHttpPort()); + $this->assertEquals($expectedHttpsPort, $context->getHttpsPort()); + $this->assertEquals(0 === strpos($uri, 'https') ? 'https' : 'http', $context->getScheme()); + } + + public function getPortData() + { + return array( + array(80, 443, 'http://localhost/', 80, 443), + array(80, 443, 'http://localhost:90/', 90, 443), + array(80, 443, 'https://localhost/', 80, 443), + array(80, 443, 'https://localhost:90/', 80, 90), + ); + } + + /** + * @param string $uri + * + * @return GetResponseEvent + */ + private function createGetResponseEventForUri($uri) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create($uri); + $request->attributes->set('_controller', null); // Prevents going in to routing process + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMatcher() + { + new RouterListener(new \stdClass(), $this->requestStack); + } + + public function testRequestMatcher() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + } + + public function testSubRequestWithDifferentMethod() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'post'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->any()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $context = new RequestContext(); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + + // sub-request with another HTTP method + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'get'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertEquals('GET', $context->getMethod()); + } + + /** + * @dataProvider getLoggingParameterData + */ + public function testLoggingParameter($parameter, $log, $parameters) + { + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->will($this->returnValue($parameter)); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('info') + ->with($this->equalTo($log), $this->equalTo($parameters)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext(), $logger); + $listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST)); + } + + public function getLoggingParameterData() + { + return array( + array(array('_route' => 'foo'), 'Matched route "{route}".', array('route' => 'foo', 'route_parameters' => array('_route' => 'foo'), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + array(array(), 'Matched route "{route}".', array('route' => 'n/a', 'route_parameters' => array(), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + ); + } + + public function testWithBadRequest() + { + $requestStack = new RequestStack(); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->never())->method('matchRequest'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new ValidateRequestListener()); + $dispatcher->addSubscriber(new RouterListener($requestMatcher, $requestStack, new RequestContext())); + $dispatcher->addSubscriber(new ExceptionListener(function () { + return new Response('Exception handled', 400); + })); + + $kernel = new HttpKernel($dispatcher, new ControllerResolver(), $requestStack, new ArgumentResolver()); + + $request = Request::create('http://localhost/'); + $request->headers->set('host', '###'); + $response = $kernel->handle($request); + $this->assertSame(400, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php new file mode 100644 index 0000000000..1e79ebe052 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php @@ -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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class SurrogateListenerTest extends TestCase +{ + public function testFilterDoesNothingForSubRequests() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo <esi:include src="" />'); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsSomeEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo <esi:include src="" />'); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('content="ESI/1.0"', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsNoEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo'); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php new file mode 100644 index 0000000000..8ada8e7dd8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php @@ -0,0 +1,145 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\EventListener\SessionListener; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * SessionListenerTest. + * + * Tests SessionListener. + * + * @author Bulat Shakirzyanov <mallluhuct@gmail.com> + */ +class TestSessionListenerTest extends TestCase +{ + /** + * @var TestSessionListener + */ + private $listener; + + /** + * @var SessionInterface + */ + private $session; + + protected function setUp() + { + $this->listener = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener'); + $this->session = $this->getSession(); + } + + public function testShouldSaveMasterRequestSession() + { + $this->sessionHasBeenStarted(); + $this->sessionMustBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testShouldNotSaveSubRequestSession() + { + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request(), HttpKernelInterface::SUB_REQUEST); + } + + public function testDoesNotDeleteCookieIfUsingSessionLifetime() + { + $this->sessionHasBeenStarted(); + + $params = session_get_cookie_params(); + session_set_cookie_params(0, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + + $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST); + $cookies = $response->headers->getCookies(); + + $this->assertEquals(0, reset($cookies)->getExpiresTime()); + } + + public function testUnstartedSessionIsNotSave() + { + $this->sessionHasNotBeenStarted(); + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testDoesNotImplementServiceSubscriberInterface() + { + $this->assertTrue(interface_exists(ServiceSubscriberInterface::class)); + $this->assertTrue(class_exists(SessionListener::class)); + $this->assertTrue(class_exists(TestSessionListener::class)); + $this->assertFalse(is_subclass_of(SessionListener::class, ServiceSubscriberInterface::class), 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford'); + $this->assertFalse(is_subclass_of(TestSessionListener::class, ServiceSubscriberInterface::class, 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford')); + } + + private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) + { + $request->setSession($this->session); + $response = new Response(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $event = new FilterResponseEvent($kernel, $request, $type, $response); + + $this->listener->onKernelResponse($event); + + $this->assertSame($response, $event->getResponse()); + + return $response; + } + + private function sessionMustNotBeSaved() + { + $this->session->expects($this->never()) + ->method('save'); + } + + private function sessionMustBeSaved() + { + $this->session->expects($this->once()) + ->method('save'); + } + + private function sessionHasBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + } + + private function sessionHasNotBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + } + + private function getSession() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') + ->disableOriginalConstructor() + ->getMock(); + + // set return value for getName() + $mock->expects($this->any())->method('getName')->will($this->returnValue('MOCKSESSID')); + + return $mock; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php new file mode 100644 index 0000000000..23b833177a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php @@ -0,0 +1,118 @@ +<?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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class TranslatorListenerTest extends TestCase +{ + private $listener; + private $translator; + private $requestStack; + + protected function setUp() + { + $this->translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock(); + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $this->listener = new TranslatorListener($this->translator, $this->requestStack); + } + + public function testLocaleIsSetInOnKernelRequest() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testLocaleIsSetInOnKernelFinishRequestWhenParentRequestExists() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testLocaleIsNotSetInOnKernelFinishRequestWhenParentRequestDoesNotExist() + { + $this->translator + ->expects($this->never()) + ->method('setLocale'); + + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelFinishRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + private function createHttpKernel() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + private function createRequest($locale) + { + $request = new Request(); + $request->setLocale($locale); + + return $request; + } + + private function setMasterRequest($request) + { + $this->requestStack + ->expects($this->any()) + ->method('getParentRequest') + ->will($this->returnValue($request)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php new file mode 100644 index 0000000000..d060943255 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php @@ -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\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +class ValidateRequestListenerTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + */ + public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest')); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $dispatcher->dispatch(KernelEvents::REQUEST, $event); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php new file mode 100644 index 0000000000..2bfcb2bf80 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; + +class AccessDeniedHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new AccessDeniedHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php new file mode 100644 index 0000000000..5fd54ccf44 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; + +class BadRequestHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new BadRequestHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php new file mode 100644 index 0000000000..63cb49e6e0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; + +class ConflictHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new ConflictHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php new file mode 100644 index 0000000000..ec5339d2af --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\GoneHttpException; + +class GoneHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new GoneHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php new file mode 100644 index 0000000000..b64773551e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Exception\HttpException; + +class HttpExceptionTest extends TestCase +{ + public function headerDataProvider() + { + return array( + array(array('X-Test' => 'Test')), + array(array('X-Test' => 1)), + array( + array( + array('X-Test' => 'Test'), + array('X-Test-2' => 'Test-2'), + ), + ), + ); + } + + public function testHeadersDefault() + { + $exception = $this->createException(); + $this->assertSame(array(), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersConstructor($headers) + { + $exception = new HttpException(200, null, null, $headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = $this->createException(); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new HttpException(200); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php new file mode 100644 index 0000000000..462d3ca4fc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException; + +class LengthRequiredHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new LengthRequiredHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php new file mode 100644 index 0000000000..b5def13ce3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php @@ -0,0 +1,24 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; + +class MethodNotAllowedHttpExceptionTest extends HttpExceptionTest +{ + public function testHeadersDefault() + { + $exception = new MethodNotAllowedHttpException(array('GET', 'PUT')); + $this->assertSame(array('Allow' => 'GET, PUT'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new MethodNotAllowedHttpException(array('GET')); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php new file mode 100644 index 0000000000..4c0db7a3cb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; + +class NotAcceptableHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new NotAcceptableHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php new file mode 100644 index 0000000000..62ede5b4b8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +class NotFoundHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new NotFoundHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php new file mode 100644 index 0000000000..809252b757 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; + +class PreconditionFailedHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new PreconditionFailedHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php new file mode 100644 index 0000000000..69d362e3f6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php @@ -0,0 +1,13 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; + +class PreconditionRequiredHttpExceptionTest extends HttpExceptionTest +{ + protected function createException() + { + return new PreconditionRequiredHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php new file mode 100644 index 0000000000..e41a23d4e7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php @@ -0,0 +1,29 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; + +class ServiceUnavailableHttpExceptionTest extends HttpExceptionTest +{ + public function testHeadersDefaultRetryAfter() + { + $exception = new ServiceUnavailableHttpException(10); + $this->assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new ServiceUnavailableHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new ServiceUnavailableHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php new file mode 100644 index 0000000000..2079bb3380 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php @@ -0,0 +1,29 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; + +class TooManyRequestsHttpExceptionTest extends HttpExceptionTest +{ + public function testHeadersDefaultRertyAfter() + { + $exception = new TooManyRequestsHttpException(10); + $this->assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new TooManyRequestsHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new TooManyRequestsHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php new file mode 100644 index 0000000000..37a0028dc8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php @@ -0,0 +1,24 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; + +class UnauthorizedHttpExceptionTest extends HttpExceptionTest +{ + public function testHeadersDefault() + { + $exception = new UnauthorizedHttpException('Challenge'); + $this->assertSame(array('WWW-Authenticate' => 'Challenge'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new UnauthorizedHttpException('Challenge'); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php new file mode 100644 index 0000000000..760366c694 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php @@ -0,0 +1,28 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; + +class UnprocessableEntityHttpExceptionTest extends HttpExceptionTest +{ + /** + * Test that setting the headers using the setter function + * is working as expected. + * + * @param array $headers The headers to set + * + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new UnprocessableEntityHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new UnprocessableEntityHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php new file mode 100644 index 0000000000..d47287a1fb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php @@ -0,0 +1,23 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Exception; + +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; + +class UnsupportedMediaTypeHttpExceptionTest extends HttpExceptionTest +{ + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new UnsupportedMediaTypeHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException($headers = array()) + { + return new UnsupportedMediaTypeHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php new file mode 100644 index 0000000000..b6cf1cba20 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php @@ -0,0 +1,37 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\_123; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class Kernel123 extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/cache/'.$this->environment; + } + + public function getLogDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/logs'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php new file mode 100644 index 0000000000..e8e0b60346 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php @@ -0,0 +1,19 @@ +<?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\Tests\Fixtures\Controller; + +class BasicTypesController +{ + public function action(string $foo, int $bar, float $baz) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php new file mode 100644 index 0000000000..9b4754b46f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Request; + +class ExtendingRequest extends Request +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php new file mode 100644 index 0000000000..9fa54ee8bb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Session\Session; + +class ExtendingSession extends Session +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php new file mode 100644 index 0000000000..9db4df7b4c --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php @@ -0,0 +1,19 @@ +<?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\Tests\Fixtures\Controller; + +class NullableController +{ + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php new file mode 100644 index 0000000000..c39812453b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php @@ -0,0 +1,19 @@ +<?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\Tests\Fixtures\Controller; + +class VariadicController +{ + public function action($foo, ...$bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php new file mode 100644 index 0000000000..867ccdce57 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php @@ -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\Tests\Fixtures\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; + +class CloneVarDataCollector extends DataCollector +{ + private $varToClone; + + public function __construct($varToClone) + { + $this->varToClone = $varToClone; + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = $this->cloneVar($this->varToClone); + } + + public function getData() + { + return $this->data; + } + + public function getName() + { + return 'clone_var'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php new file mode 100644 index 0000000000..c8bfd36e66 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\ExtensionAbsentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionAbsentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php new file mode 100644 index 0000000000..b43bc665a8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php @@ -0,0 +1,22 @@ +<?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\Tests\Fixtures\ExtensionLoadedBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php new file mode 100644 index 0000000000..3af81cb073 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\ExtensionLoadedBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionLoadedBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php new file mode 100644 index 0000000000..0fd64316fb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php @@ -0,0 +1,20 @@ +<?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\Tests\Fixtures\ExtensionNotValidBundle\DependencyInjection; + +class ExtensionNotValidExtension +{ + public function getAlias() + { + return 'extension_not_valid'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php new file mode 100644 index 0000000000..34e2920392 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\ExtensionNotValidBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionNotValidBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php new file mode 100644 index 0000000000..977976b75f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php @@ -0,0 +1,17 @@ +<?php + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; + +use Symfony\Component\Console\Command\Command; + +/** + * This command has a required parameter on the constructor and will be ignored by the default Bundle implementation. + * + * @see Bundle::registerCommands() + */ +class BarCommand extends Command +{ + public function __construct($example, $name = 'bar') + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php new file mode 100644 index 0000000000..c6570aa046 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; + +use Symfony\Component\Console\Command\Command; + +class FooCommand extends Command +{ + protected function configure() + { + $this->setName('foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php new file mode 100644 index 0000000000..108571718d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php @@ -0,0 +1,22 @@ +<?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\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionPresentExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php new file mode 100644 index 0000000000..36a7ad404a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php @@ -0,0 +1,18 @@ +<?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\Tests\Fixtures\ExtensionPresentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionPresentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php new file mode 100644 index 0000000000..a1102ab784 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForOverrideName extends Kernel +{ + protected $name = 'overridden'; + + public function registerBundles() + { + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php new file mode 100644 index 0000000000..5fd61bbc7e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php @@ -0,0 +1,37 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForTest extends Kernel +{ + public function getBundleMap() + { + return $this->bundleMap; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function isBooted() + { + return $this->booted; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php new file mode 100644 index 0000000000..cee1b09fb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php @@ -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\Tests\Fixtures; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; + +class KernelWithoutBundles extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + protected function build(ContainerBuilder $container) + { + $container->setParameter('test_executed', true); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php new file mode 100644 index 0000000000..e7d60cff3d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php @@ -0,0 +1,31 @@ +<?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\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Client; + +class TestClient extends Client +{ + protected function getScript($request) + { + $script = parent::getScript($request); + + $autoload = file_exists(__DIR__.'/../../vendor/autoload.php') + ? __DIR__.'/../../vendor/autoload.php' + : __DIR__.'/../../../../../../vendor/autoload.php' + ; + + $script = preg_replace('/(\->register\(\);)/', "$0\nrequire_once '$autoload';\n", $script); + + return $script; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php new file mode 100644 index 0000000000..da7ef5bd60 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestEventDispatcher extends EventDispatcher implements TraceableEventDispatcherInterface +{ + public function getCalledListeners() + { + return array('foo'); + } + + public function getNotCalledListeners() + { + return array('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php new file mode 100644 index 0000000000..bfe922e22c --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php @@ -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\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class EsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfEsiNotSupported() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + /** + * @group legacy + * @expectedDeprecation Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated %s. + */ + public function testRenderFallbackWithObjectAttributesIsDeprecated() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo')); + $request = Request::create('/'); + $reference = new ControllerReference('main_controller', array('foo' => array('a' => array(), 'b' => new \stdClass())), array()); + $strategy->render($reference, $request); + } + + public function testRender() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $this->assertEquals('<esi:include src="/" />', $strategy->render('/', $request)->getContent()); + $this->assertEquals("<esi:comment text=\"This is a comment\" />\n<esi:include src=\"/\" />", $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent()); + $this->assertEquals('<esi:include src="/" alt="foo" />', $strategy->render('/', $request, array('alt' => 'foo'))->getContent()); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '<esi:include src="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dmain_controller&_hash=Jz1P8NErmhKTeI6onI1EdAXTB85359MY3RIk5mSJ60w%3D" alt="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dalt_controller&_hash=iPJEdRoUpGrM1ztqByiorpfMPtiW%2FOWwdH1DBUXHhEc%3D" />', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php new file mode 100644 index 0000000000..9c906b50a0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php @@ -0,0 +1,99 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class FragmentHandlerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack') + ->disableOriginalConstructor() + ->getMock() + ; + $this->requestStack + ->expects($this->any()) + ->method('getCurrentRequest') + ->will($this->returnValue(Request::create('/'))) + ; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWhenRendererDoesNotExist() + { + $handler = new FragmentHandler($this->requestStack); + $handler->render('/', 'foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWithUnknownRenderer() + { + $handler = $this->getHandler($this->returnValue(new Response('foo'))); + + $handler->render('/', 'bar'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Error when rendering "http://localhost/" (Status code is 404). + */ + public function testDeliverWithUnsuccessfulResponse() + { + $handler = $this->getHandler($this->returnValue(new Response('foo', 404))); + + $handler->render('/', 'foo'); + } + + public function testRender() + { + $handler = $this->getHandler($this->returnValue(new Response('foo')), array('/', Request::create('/'), array('foo' => 'foo', 'ignore_errors' => true))); + + $this->assertEquals('foo', $handler->render('/', 'foo', array('foo' => 'foo'))); + } + + protected function getHandler($returnValue, $arguments = array()) + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')) + ; + $e = $renderer + ->expects($this->any()) + ->method('render') + ->will($returnValue) + ; + + if ($arguments) { + call_user_func_array(array($e, 'with'), $arguments); + } + + $handler = new FragmentHandler($this->requestStack); + $handler->addRenderer($renderer); + + return $handler; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php new file mode 100644 index 0000000000..1be052e5e6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php @@ -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\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpFoundation\Request; + +class HIncludeFragmentRendererTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testRenderExceptionWhenControllerAndNoSigner() + { + $strategy = new HIncludeFragmentRenderer(); + $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/')); + } + + public function testRenderWithControllerAndSigner() + { + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + + $this->assertEquals('<hx:include src="/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller&_hash=BP%2BOzCD5MRUI%2BHJpgPDOmoju00FnzLhP3TGcSHbbBLs%3D"></hx:include>', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithUri() + { + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('<hx:include src="/foo"></hx:include>', $strategy->render('/foo', Request::create('/'))->getContent()); + + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + $this->assertEquals('<hx:include src="/foo"></hx:include>', $strategy->render('/foo', Request::create('/'))->getContent()); + } + + public function testRenderWithDefault() + { + // only default + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('<hx:include src="/foo">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + + // only global default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('<hx:include src="/foo">global_default</hx:include>', $strategy->render('/foo', Request::create('/'), array())->getContent()); + + // global default and default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('<hx:include src="/foo">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } + + public function testRenderWithAttributesOptions() + { + // with id + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('<hx:include src="/foo" id="bar">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar'))->getContent()); + + // with attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('<hx:include src="/foo" p1="v1" p2="v2">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + + // with id & attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('<hx:include src="/foo" p1="v1" p2="v2" id="bar">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + } + + public function testRenderWithDefaultText() + { + $engine = $this->getMockBuilder('Symfony\\Component\\Templating\\EngineInterface')->getMock(); + $engine->expects($this->once()) + ->method('exists') + ->with('default') + ->will($this->throwException(new \InvalidArgumentException())); + + // only default + $strategy = new HIncludeFragmentRenderer($engine); + $this->assertEquals('<hx:include src="/foo">default</hx:include>', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php new file mode 100644 index 0000000000..18e55a5be0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -0,0 +1,250 @@ +<?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\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class InlineFragmentRendererTest extends TestCase +{ + public function testRender() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderWithControllerReference() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithObjectsAsAttributes() + { + $object = new \stdClass(); + + $subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller'); + $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); + $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); + + $this->assertSame('foo', $strategy->render(new ControllerReference('main_controller', array('object' => $object), array()), Request::create('/'))->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheControllerLegacy() + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver')->setMethods(array('getController'))->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheController() + { + $resolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack(), new ArgumentResolver()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + public function testRenderWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); + + Request::setTrustedProxies(array(), -1); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->never())->method('dispatch'); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderExceptionIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->once())->method('dispatch')->with(KernelEvents::EXCEPTION); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEmpty($strategy->render('/', Request::create('/'), array('ignore_errors' => true))->getContent()); + } + + public function testRenderExceptionIgnoreErrorsWithAlt() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->onConsecutiveCalls( + $this->throwException(new \RuntimeException('foo')), + $this->returnValue(new Response('bar')) + ))); + + $this->assertEquals('bar', $strategy->render('/', Request::create('/'), array('ignore_errors' => true, 'alt' => '/foo'))->getContent()); + } + + private function getKernel($returnValue) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $controllerResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $controllerResolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + + $argumentResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface')->getMock(); + $argumentResolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $controllerResolver, new RequestStack(), $argumentResolver); + $renderer = new InlineFragmentRenderer($kernel); + + // simulate a main request with output buffering + ob_start(); + echo 'Foo'; + + // simulate a sub-request with output buffering and an exception + $renderer->render('/', Request::create('/'), array('ignore_errors' => true)); + + $this->assertEquals('Foo', ob_get_clean()); + } + + public function testESIHeaderIsKeptInSubrequest() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + } + + public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $this->testESIHeaderIsKeptInSubrequest(); + + Request::setTrustedProxies(array(), -1); + } + + public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() + { + $expectedSubRequest = Request::create('/'); + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + $request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*')); + $strategy->render('/', $request); + } + + /** + * Creates a Kernel expecting a request equals to $request + * Allows delta in comparison in case REQUEST_TIME changed by 1 second. + */ + private function getKernelExpectingRequest(Request $request, $strict = false) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->once()) + ->method('handle') + ->with($this->equalTo($request, 1)) + ->willReturn(new Response('foo')); + + return $kernel; + } +} + +class Bar +{ + public $bar = 'bar'; + + public function getBar() + { + return $this->bar; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php new file mode 100644 index 0000000000..3a040dedd6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +class RoutableFragmentRendererTest extends TestCase +{ + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateFragmentUri($uri, $controller) + { + $this->assertEquals($uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'))); + } + + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateAbsoluteFragmentUri($uri, $controller) + { + $this->assertEquals('http://localhost'.$uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'), true)); + } + + public function getGenerateFragmentUriData() + { + return array( + array('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array())), + array('/_fragment?_path=_format%3Dxml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('_format' => 'xml'), array())), + array('/_fragment?_path=foo%3Dfoo%26_format%3Djson%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo', '_format' => 'json'), array())), + array('/_fragment?bar=bar&_path=foo%3Dfoo%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo'), array('bar' => 'bar'))), + array('/_fragment?foo=foo&_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array('foo' => 'foo'))), + array('/_fragment?_path=foo%255B0%255D%3Dfoo%26foo%255B1%255D%3Dbar%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => array('foo', 'bar')), array())), + ); + } + + public function testGenerateFragmentUriWithARequest() + { + $request = Request::create('/'); + $request->attributes->set('_format', 'json'); + $request->setLocale('fr'); + $controller = new ControllerReference('controller', array(), array()); + + $this->assertEquals('/_fragment?_path=_format%3Djson%26_locale%3Dfr%26_controller%3Dcontroller', $this->callGenerateFragmentUriMethod($controller, $request)); + } + + /** + * @expectedException \LogicException + * @dataProvider getGenerateFragmentUriDataWithNonScalar + */ + public function testGenerateFragmentUriWithNonScalar($controller) + { + $this->callGenerateFragmentUriMethod($controller, Request::create('/')); + } + + public function getGenerateFragmentUriDataWithNonScalar() + { + return array( + array(new ControllerReference('controller', array('foo' => new Foo(), 'bar' => 'bar'), array())), + array(new ControllerReference('controller', array('foo' => array('foo' => 'foo'), 'bar' => array('bar' => new Foo())), array())), + ); + } + + private function callGenerateFragmentUriMethod(ControllerReference $reference, Request $request, $absolute = false) + { + $renderer = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer'); + $r = new \ReflectionObject($renderer); + $m = $r->getMethod('generateFragmentUri'); + $m->setAccessible(true); + + return $m->invoke($renderer, $reference, $request, $absolute); + } +} + +class Foo +{ + public $foo; + + public function getFoo() + { + return $this->foo; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php new file mode 100644 index 0000000000..b537625f24 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php @@ -0,0 +1,97 @@ +<?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\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Ssi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class SsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfSsiNotSupported() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRender() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $this->assertEquals('<!--#include virtual="/" -->', $strategy->render('/', $request)->getContent()); + $this->assertEquals('<!--#include virtual="/" -->', $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent(), 'Strategy options should not impact the ssi include tag'); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '<!--#include virtual="/_fragment?_path=_format%3Dhtml%26_locale%3Dfr%26_controller%3Dmain_controller&_hash=Jz1P8NErmhKTeI6onI1EdAXTB85359MY3RIk5mSJ60w%3D" -->', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php new file mode 100644 index 0000000000..a8662b2a2b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php @@ -0,0 +1,248 @@ +<?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\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class EsiTest extends TestCase +{ + public function testHasSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $this->assertTrue($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0", symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $esi = new Esi(); + + $response = new Response('foo <esi:include src="" />'); + $esi->addSurrogateControl($response); + $this->assertEquals('content="ESI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $esi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsEsiParsing() + { + $esi = new Esi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $this->assertTrue($esi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($esi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $esi = new Esi(); + + $this->assertEquals('<esi:include src="/" onerror="continue" alt="/alt" />', $esi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('<esi:include src="/" alt="/alt" />', $esi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('<esi:include src="/" onerror="continue" />', $esi->renderIncludeTag('/')); + $this->assertEquals('<esi:comment text="some comment" />'."\n".'<esi:include src="/" onerror="continue" alt="/alt" />', $esi->renderIncludeTag('/', '/alt', true, 'some comment')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $esi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testMultilineEsiRemoveTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('<esi:remove> <a href="http://www.example.com">www.example.com</a> </esi:remove> Keep this'."<esi:remove>\n <a>www.example.com</a> </esi:remove> And this"); + $esi->process($request, $response); + + $this->assertEquals(' Keep this And this', $response->getContent()); + } + + public function testCommentTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('<esi:comment text="some comment >" /> Keep this'); + $esi->process($request, $response); + + $this->assertEquals(' Keep this', $response->getContent()); + } + + public function testProcess() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo <esi:comment text="some comment" /><esi:include src="..." alt="alt" onerror="continue" />'); + $esi->process($request, $response); + + $this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response = new Response('foo <esi:comment text="some comment" /><esi:include src="foo\'" alt="bar\'" onerror="continue" />'); + $esi->process($request, $response); + + $this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent()); + + $response = new Response('foo <esi:include src="..." />'); + $esi->process($request, $response); + + $this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + + $response = new Response('foo <esi:include src="..."></esi:include>'); + $esi->process($request, $response); + + $this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('<?php <? <% <script language=php>'); + $esi->process($request, $response); + + $this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnEsi() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo <esi:include />'); + $esi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo <esi:include src="..." />'); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="ESI/1.0", no-store'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $esi = new Esi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $esi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $esi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $esi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $esi = new Esi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $esi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php new file mode 100644 index 0000000000..a24380c406 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php @@ -0,0 +1,1367 @@ +<?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\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @group time-sensitive + */ +class HttpCacheTest extends HttpCacheTestCase +{ + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + $storeMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface') + ->disableOriginalConstructor() + ->getMock(); + + // does not implement TerminableInterface + $kernel = new TestKernel(); + $httpCache = new HttpCache($kernel, $storeMock); + $httpCache->terminate(Request::create('/'), new Response()); + + $this->assertFalse($kernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration')) + ->getMock(); + + $kernelMock->expects($this->once()) + ->method('terminate'); + + $kernel = new HttpCache($kernelMock, $storeMock); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testPassesOnNonGetHeadRequests() + { + $this->setNextResponse(200); + $this->request('POST', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('pass'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testInvalidatesOnPostPutDeleteRequests() + { + foreach (array('post', 'put', 'delete') as $method) { + $this->setNextResponse(200); + $this->request($method, '/'); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + } + } + + public function testDoesNotCacheWithAuthorizationRequestHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesCacheWithAuthorizationRequestHeaderAndPublicResponse() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + $this->assertEquals('public', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotCacheWithCookieHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheRequestsWithACookieHeader() + { + $this->setNextResponse(200); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304WhenIfNoneMatchMatchesETag() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345')); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertTrue($this->response->headers->has('ETag')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), '', function ($request, $response) use ($time) { + $response->setStatusCode(200); + $response->headers->set('ETag', '12345'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('Hello World'); + }); + + // only ETag matches + $t = \DateTime::createFromFormat('U', time() - 3600); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // only Last-Modified matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // Both matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + } + + public function testIncrementsMaxAgeWhenNoDateIsSpecifiedEventWhenUsingETag() + { + $this->setNextResponse( + 200, + array( + 'ETag' => '1234', + 'Cache-Control' => 'public, s-maxage=60', + ) + ); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + sleep(2); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertEquals(2, $this->response->headers->get('Age')); + } + + public function testValidatesPrivateResponsesCachedOnTheClient() + { + $this->setNextResponse(200, array(), '', function ($request, $response) { + $etags = preg_split('/\s*,\s*/', $request->headers->get('IF_NONE_MATCH')); + if ($request->cookies->has('authenticated')) { + $response->headers->set('Cache-Control', 'private, no-store'); + $response->setETag('"private tag"'); + if (in_array('"private tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('private data'); + } + } else { + $response->headers->set('Cache-Control', 'public'); + $response->setETag('"public tag"'); + if (in_array('"public tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('public data'); + } + } + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"public tag"', $this->response->headers->get('ETag')); + $this->assertEquals('public data', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array(), array('authenticated' => '')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"private tag"', $this->response->headers->get('ETag')); + $this->assertEquals('private data', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceNotContains('store'); + } + + public function testStoresResponsesWhenNoCacheRequestDirectivePresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testReloadsResponsesWhenCacheHitsButNoCacheRequestDirectivePresentWhenAllowReloadIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('reload'); + $this->assertTraceContains('store'); + } + + public function testDoesNotReloadResponsesWhenAllowReloadIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + } + + public function testRevalidatesFreshCacheEntryWhenMaxAgeRequestDirectiveIsExceededWhenAllowRevalidateOptionIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testDoesNotRevalidateFreshCacheEntryWhenEnableRevalidateOptionIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + } + + public function testFetchesResponseFromBackendWhenCacheMisses() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testDoesNotCacheSomeStatusCodeResponses() + { + foreach (array_merge(range(201, 202), range(204, 206), range(303, 305), range(400, 403), range(405, 409), range(411, 417), range(500, 505)) as $code) { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse($code, array('Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals($code, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + } + + public function testDoesNotCacheResponsesWithExplicitNoStoreDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-store')); + + $this->request('GET', '/'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheResponsesWithoutFreshnessInformationOrAValidator() + { + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + } + + public function testCachesResponsesWithExplicitNoCacheDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache')); + + $this->request('GET', '/'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testCachesResponsesWithAnExpirationHeader() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithAMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithASMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 's-maxage=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testHitsCachedResponsesWithExpiresHeader() + { + $time1 = \DateTime::createFromFormat('U', time() - 5); + $time2 = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testHitsCachedResponseWithMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testDegradationWhenCacheLocked() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Skips on windows to avoid permissions issues.'); + } + + $this->cacheConfig['stale_while_revalidate'] = 10; + + // The prescence of Last-Modified makes this cacheable (because Response::isValidateable() then). + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=5', 'Last-Modified' => 'some while ago'), 'Old response'); + $this->request('GET', '/'); // warm the cache + + // Now, lock the cache + $concurrentRequest = Request::create('/', 'GET'); + $this->store->lock($concurrentRequest); + + /* + * After 10s, the cached response has become stale. Yet, we're still within the "stale_while_revalidate" + * timeout so we may serve the stale response. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale-while-revalidate'); + $this->assertEquals('Old response', $this->response->getContent()); + + /* + * Another 10s later, stale_while_revalidate is over. Resort to serving the old response, but + * do so with a "server unavailable" message. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(503, $this->response->getStatusCode()); + $this->assertEquals('Old response', $this->response->getContent()); + } + + public function testHitsCachedResponseWithSMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 's-maxage=10, max-age=0')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformation() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpired() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpiredWithStatus304() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotAssignDefaultTtlWhenResponseHasMustRevalidateDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'must-revalidate')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertNotRegExp('/s-maxage/', $this->response->headers->get('Cache-Control')); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertNotNull($this->response->headers->get('Age')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // go in and play around with the cached metadata directly ... + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time()); + $tmp[0][1]['expires'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('stale'); + $this->assertTraceNotContains('fresh'); + $this->assertTraceNotContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('stale'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testValidatesCachedResponsesUseSameHttpMethod() + { + $test = $this; + + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($test) { + $test->assertSame('OPTIONS', $request->getMethod()); + }); + + // build initial request + $this->request('OPTIONS', '/'); + + // build subsequent request + $this->request('OPTIONS', '/'); + } + + public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('ETag', '"12345"'); + if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testServesResponseWhileFreshAndRevalidatesWithLastModifiedInformation() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), 'Hello World', function (Request $request, Response $response) use ($time) { + $response->setSharedMaxAge(10); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + // prime the cache + $this->request('GET', '/'); + + // next request before s-maxage has expired: Serve from cache + // without hitting the backend + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + sleep(15); // expire the cache + + $this->setNextResponse(304, array(), '', function (Request $request, Response $response) use ($time) { + $this->assertEquals($time->format(DATE_RFC2822), $request->headers->get('IF_MODIFIED_SINCE')); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + } + + public function testReplacesCachedResponsesWhenValidationResultsInNon304Response() + { + $time = \DateTime::createFromFormat('U', time()); + $count = 0; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) { + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Cache-Control', 'public'); + switch (++$count) { + case 1: + $response->setContent('first response'); + break; + case 2: + $response->setContent('second response'); + break; + case 3: + $response->setContent(''); + $response->setStatusCode(304); + break; + } + }); + + // first request should fetch from backend and store in cache + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('first response', $this->response->getContent()); + + // second request is validated, is invalid, and replaces cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + // third response is validated, valid, and returns cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + $this->assertEquals(3, $count); + } + + public function testPassesHeadRequestsThroughDirectlyOnPass() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->setContent(''); + $response->setStatusCode(200); + $this->assertEquals('HEAD', $request->getMethod()); + }); + + $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('', $this->response->getContent()); + } + + public function testUsesCacheToRespondToHeadRequestsWhenFresh() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->setContent('Hello World'); + $response->setStatusCode(200); + $this->assertNotEquals('HEAD', $request->getMethod()); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('HEAD', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length')); + } + + public function testSendsNoContentWhenFresh() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + } + + public function testInvalidatesCachedResponsesOnPost() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + if ('GET' == $request->getMethod()) { + $response->setStatusCode(200); + $response->headers->set('Cache-Control', 'public, max-age=500'); + $response->setContent('Hello World'); + } elseif ('POST' == $request->getMethod()) { + $response->setStatusCode(303); + $response->headers->set('Location', '/'); + $response->headers->remove('Cache-Control'); + $response->setContent(''); + } + }); + + // build initial request to enter into the cache + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // make sure it is valid + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + // now POST to same URL + $this->request('POST', '/helloworld'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('/', $this->response->headers->get('Location')); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + $this->assertEquals('', $this->response->getContent()); + + // now make sure it was actually invalidated + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testServesFromCacheWhenHeadersMatch() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + } + + public function testStoresMultipleResponsesWhenHeadersDiffer() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('miss'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(3, $this->response->headers->get('X-Response-Count')); + } + + public function testShouldCatchExceptions() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache')); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldNotCatchExceptions() + { + $this->catchExceptions(false); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreNotCaught(); + } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + + // check for 100 or 99 as the test can be executed after a second change + $this->assertTrue(in_array($this->response->getTtl(), array(99, 100))); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertNull($this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } + + public function testEsiRecalculateContentLengthHeader() + { + $responses = array( + array( + 'status' => 200, + 'body' => '<esi:include src="/foo" />', + 'headers' => array( + 'Content-Length' => 26, + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World!', $this->response->getContent()); + $this->assertEquals(12, $this->response->headers->get('Content-Length')); + } + + public function testClientIpIsAlwaysLocalhostForForwardedRequests() + { + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + } + + /** + * @dataProvider getTrustedProxyData + */ + public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected) + { + Request::setTrustedProxies($existing, Request::HEADER_X_FORWARDED_ALL); + + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals($expected, Request::getTrustedProxies()); + } + + public function getTrustedProxyData() + { + return array( + array(array(), array('127.0.0.1')), + array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')), + array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')), + ); + } + + /** + * @dataProvider getXForwardedForData + */ + public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + if (false !== $xForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + } + $this->request('GET', '/', $server); + + $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function getXForwardedForData() + { + return array( + array(false, '10.0.0.1'), + array('10.0.0.2', '10.0.0.2, 10.0.0.1'), + array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + ); + } + + public function testXForwarderForHeaderForPassRequests() + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + $this->request('POST', '/', $server); + + $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() + { + $time = \DateTime::createFromFormat('U', time()); + + $responses = array( + array( + 'status' => 200, + 'body' => '<esi:include src="/hey" />', + 'headers' => array( + 'Surrogate-Control' => 'content="ESI/1.0"', + 'ETag' => 'hey', + 'Last-Modified' => $time->format(DATE_RFC2822), + ), + ), + array( + 'status' => 200, + 'body' => 'Hey!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertNull($this->response->getETag()); + $this->assertNull($this->response->getLastModified()); + } + + public function testDoesNotCacheOptionsRequest() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get'); + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options'); + $this->request('OPTIONS', '/'); + $this->assertHttpKernelIsCalled(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertSame('get', $this->response->getContent()); + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate(Request $request, Response $response) + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php new file mode 100644 index 0000000000..ed5c690d60 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php @@ -0,0 +1,185 @@ +<?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\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class HttpCacheTestCase extends TestCase +{ + protected $kernel; + protected $cache; + protected $caches; + protected $cacheConfig; + protected $request; + protected $response; + protected $responses; + protected $catch; + protected $esi; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->kernel = null; + + $this->cache = null; + $this->esi = null; + $this->caches = array(); + $this->cacheConfig = array(); + + $this->request = null; + $this->response = null; + $this->responses = array(); + + $this->catch = false; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + if ($this->cache) { + $this->cache->getStore()->cleanup(); + } + $this->kernel = null; + $this->cache = null; + $this->caches = null; + $this->request = null; + $this->response = null; + $this->responses = null; + $this->cacheConfig = null; + $this->catch = null; + $this->esi = null; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function assertHttpKernelIsCalled() + { + $this->assertTrue($this->kernel->hasBeenCalled()); + } + + public function assertHttpKernelIsNotCalled() + { + $this->assertFalse($this->kernel->hasBeenCalled()); + } + + public function assertResponseOk() + { + $this->assertEquals(200, $this->response->getStatusCode()); + } + + public function assertTraceContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertTraceNotContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertNotRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertExceptionsAreCaught() + { + $this->assertTrue($this->kernel->isCatchingExceptions()); + } + + public function assertExceptionsAreNotCaught() + { + $this->assertFalse($this->kernel->isCatchingExceptions()); + } + + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false, $headers = array()) + { + if (null === $this->kernel) { + throw new \LogicException('You must call setNextResponse() before calling request().'); + } + + $this->kernel->reset(); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + + $this->cacheConfig['debug'] = true; + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); + $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); + $this->request->headers->add($headers); + + $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); + + $this->responses[] = $this->response; + } + + public function getMetaStorageValues() + { + $values = array(); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(sys_get_temp_dir().'/http_cache/md', \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + $values[] = file_get_contents($file); + } + + return $values; + } + + // A basic response with 200 status code and a tiny body. + public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) + { + $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); + } + + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + + public function catchExceptions($catch = true) + { + $this->catch = $catch; + } + + public static function clearDirectory($directory) + { + if (!is_dir($directory)) { + return; + } + + $fp = opendir($directory); + while (false !== $file = readdir($fp)) { + if (!in_array($file, array('.', '..'))) { + if (is_link($directory.'/'.$file)) { + unlink($directory.'/'.$file); + } elseif (is_dir($directory.'/'.$file)) { + self::clearDirectory($directory.'/'.$file); + rmdir($directory.'/'.$file); + } else { + unlink($directory.'/'.$file); + } + } + } + + closedir($fp); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php new file mode 100644 index 0000000000..5e4c322223 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -0,0 +1,222 @@ +<?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\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy; + +class ResponseCacheStrategyTest extends TestCase +{ + public function testMinimumSharedMaxAgeWins() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInAnyEmbeddedRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInMasterRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } + + public function testMasterResponseNotCacheableWhenEmbeddedResponseRequiresValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $embeddedResponse = new Response(); + $embeddedResponse->setLastModified(new \DateTime()); + $cacheStrategy->add($embeddedResponse); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testValidationOnMasterResponseIsNotPossibleWhenItContainsEmbeddedResponses() + { + $cacheStrategy = new ResponseCacheStrategy(); + + // This master response uses the "validation" model + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $masterResponse->setEtag('foo'); + + // Embedded response uses "expiry" model + $embeddedResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->add($embeddedResponse); + + $cacheStrategy->update($masterResponse); + + $this->assertFalse($masterResponse->isValidateable()); + $this->assertFalse($masterResponse->headers->has('Last-Modified')); + $this->assertFalse($masterResponse->headers->has('ETag')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + } + + public function testMasterResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isValidateable()); + } + + public function testMasterResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isFresh()); + } + + public function testMasterResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // Public, cacheable + + /* This response has no validation or expiration information. + That makes it uncacheable, it is always stale. + (It does *not* make this private, though.) */ + $embeddedResponse = new Response(); + $this->assertFalse($embeddedResponse->isFresh()); // not fresh, as no lifetime is provided + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testEmbeddingPrivateResponseMakesMainResponsePrivate() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // public, cacheable + + // The embedded response might for example contain per-user data that remains valid for 60 seconds + $embeddedResponse = new Response(); + $embeddedResponse->setPrivate(); + $embeddedResponse->setMaxAge(60); // this would implicitly set "private" as well, but let's be explicit + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); + // Not sure if we should pass "max-age: 60" in this case, as long as the response is private and + // that's the more conservative of both the master and embedded response...? + } + + public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation() + { + /* When "expiration wins over validation" (https://symfony.com/doc/current/http_cache/validation.html) + * and both the main and embedded response provide s-maxage, then the more restricting value of both + * should be fine, regardless of whether the embedded response can be validated later on or must be + * completely regenerated. + */ + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + $embeddedResponse->setEtag('foo'); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + } + + public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombinesExpirationAndValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $masterResponse->setEtag('foo'); + $masterResponse->setLastModified(new \DateTime()); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + $this->assertFalse($masterResponse->isValidateable()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php new file mode 100644 index 0000000000..1079d37a27 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php @@ -0,0 +1,215 @@ +<?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\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +class SsiTest extends TestCase +{ + public function testHasSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="SSI/1.0"'); + $this->assertTrue($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0", symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $ssi = new Ssi(); + + $response = new Response('foo <!--#include virtual="" -->'); + $ssi->addSurrogateControl($response); + $this->assertEquals('content="SSI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $ssi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsSsiParsing() + { + $ssi = new Ssi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $this->assertTrue($ssi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($ssi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $ssi = new Ssi(); + + $this->assertEquals('<!--#include virtual="/" -->', $ssi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('<!--#include virtual="/" -->', $ssi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('<!--#include virtual="/" -->', $ssi->renderIncludeTag('/')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $ssi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testProcess() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo <!--#include virtual="..." -->'); + $ssi->process($request, $response); + + $this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response = new Response('foo <!--#include virtual="foo\'" -->'); + $ssi->process($request, $response); + + $this->assertEquals("foo <?php echo \$this->surrogate->handle(\$this, 'foo\\'', '', false) ?>"."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('<?php <? <% <script language=php>'); + $ssi->process($request, $response); + + $this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnSsi() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo <!--#include -->'); + $ssi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo <!--#include virtual="..." -->'); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="SSI/1.0", no-store'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $ssi = new Ssi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $ssi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $ssi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $ssi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $ssi = new Ssi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $ssi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php new file mode 100644 index 0000000000..cef019167a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php @@ -0,0 +1,301 @@ +<?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\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Store; + +class StoreTest extends TestCase +{ + protected $request; + protected $response; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->request = Request::create('/'); + $this->response = new Response('hello world', 200, array()); + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + $this->store = null; + $this->request = null; + $this->response = null; + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey() + { + $this->assertEmpty($this->getStoreMetadata('/nothing')); + } + + public function testUnlockFileThatDoesExist() + { + $cacheKey = $this->storeSimpleEntry(); + $this->store->lock($this->request); + + $this->assertTrue($this->store->unlock($this->request)); + } + + public function testUnlockFileThatDoesNotExist() + { + $this->assertFalse($this->store->unlock($this->request)); + } + + public function testRemovesEntriesForKeyWithPurge() + { + $request = Request::create('/foo'); + $this->store->write($request, new Response('foo')); + + $metadata = $this->getStoreMetadata($request); + $this->assertNotEmpty($metadata); + + $this->assertTrue($this->store->purge('/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + + // cached content should be kept after purging + $path = $this->store->getPath($metadata[0][1]['x-content-digest'][0]); + $this->assertTrue(is_file($path)); + + $this->assertFalse($this->store->purge('/bar')); + } + + public function testStoresACacheEntry() + { + $cacheKey = $this->storeSimpleEntry(); + + $this->assertNotEmpty($this->getStoreMetadata($cacheKey)); + } + + public function testSetsTheXContentDigestResponseHeaderBeforeStoring() + { + $cacheKey = $this->storeSimpleEntry(); + $entries = $this->getStoreMetadata($cacheKey); + list($req, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + } + + public function testFindsAStoredEntryWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertNotNull($response); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + } + + public function testDoesNotFindAnEntryWithLookupWhenNoneExists() + { + $request = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + + $this->assertNull($this->store->lookup($request)); + } + + public function testCanonizesUrlsForCacheKeys() + { + $this->storeSimpleEntry($path = '/test?x=y&p=q'); + $hitsReq = Request::create($path); + $missReq = Request::create('/test?p=x'); + + $this->assertNotNull($this->store->lookup($hitsReq)); + $this->assertNull($this->store->lookup($missReq)); + } + + public function testDoesNotFindAnEntryWithLookupWhenTheBodyDoesNotExist() + { + $this->storeSimpleEntry(); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $path = $this->getStorePath($this->response->headers->get('X-Content-Digest')); + @unlink($path); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testRestoresResponseHeadersProperlyWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->getStorePath($response->headers->get('X-Content-Digest')))), $this->response->headers->all())); + } + + public function testRestoresResponseContentFromEntityStoreWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->getContent()); + } + + public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate() + { + $this->storeSimpleEntry(); + $this->store->invalidate($this->request); + $response = $this->store->lookup($this->request); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertFalse($response->isFresh()); + } + + public function testSucceedsQuietlyWhenInvalidateCalledWithNoMatchingEntries() + { + $req = Request::create('/test'); + $this->store->invalidate($req); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testDoesNotReturnEntriesThatVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testDoesNotReturnEntriesThatSlightlyVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => array('Foo', 'Bar'))); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testStoresMultipleResponsesForEachVaryCombination() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req3, $res3); + + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $this->assertCount(3, $this->getStoreMetadata($key)); + } + + public function testOverwritesNonVaryingResponseWithStore() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req3, $res3); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + + $this->assertCount(2, $this->getStoreMetadata($key)); + } + + public function testLocking() + { + $req = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $this->assertTrue($this->store->lock($req)); + + $path = $this->store->lock($req); + $this->assertTrue($this->store->isLocked($req)); + + $this->store->unlock($req); + $this->assertFalse($this->store->isLocked($req)); + } + + public function testPurgeHttps() + { + $request = Request::create('https://example.com/foo'); + $this->store->write($request, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($request)); + + $this->assertTrue($this->store->purge('https://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + } + + public function testPurgeHttpAndHttps() + { + $requestHttp = Request::create('https://example.com/foo'); + $this->store->write($requestHttp, new Response('foo')); + + $requestHttps = Request::create('http://example.com/foo'); + $this->store->write($requestHttps, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($requestHttp)); + $this->assertNotEmpty($this->getStoreMetadata($requestHttps)); + + $this->assertTrue($this->store->purge('http://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($requestHttp)); + $this->assertEmpty($this->getStoreMetadata($requestHttps)); + } + + protected function storeSimpleEntry($path = null, $headers = array()) + { + if (null === $path) { + $path = '/test'; + } + + $this->request = Request::create($path, 'get', array(), array(), array(), $headers); + $this->response = new Response('test', 200, array('Cache-Control' => 'max-age=420')); + + return $this->store->write($this->request, $this->response); + } + + protected function getStoreMetadata($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getMetadata'); + $m->setAccessible(true); + + if ($key instanceof Request) { + $m1 = $r->getMethod('getCacheKey'); + $m1->setAccessible(true); + $key = $m1->invoke($this->store, $key); + } + + return $m->invoke($this->store, $key); + } + + protected function getStorePath($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getPath'); + $m->setAccessible(true); + + return $m->invoke($this->store, $key); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php new file mode 100644 index 0000000000..946c7a31cb --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php @@ -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\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $body; + protected $status; + protected $headers; + protected $called = false; + protected $customizer; + protected $catch = false; + protected $backendRequest; + + public function __construct($body, $status, $headers, \Closure $customizer = null) + { + $this->body = $body; + $this->status = $status; + $this->headers = $headers; + $this->customizer = $customizer; + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->catch = $catch; + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function isCatchingExceptions() + { + return $this->catch; + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response($this->body, $this->status, $this->headers); + + if (null !== $customizer = $this->customizer) { + $customizer($request, $response); + } + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 0000000000..926d8daf53 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php @@ -0,0 +1,81 @@ +<?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\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $bodies = array(); + protected $statuses = array(); + protected $headers = array(); + protected $called = false; + protected $backendRequest; + + public function __construct($responses) + { + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpKernelTest.php b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php new file mode 100644 index 0000000000..7aed26aa59 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php @@ -0,0 +1,403 @@ +<?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\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class HttpKernelTest extends TestCase +{ + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithAHandlingListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException('foo'); }); + $response = $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertEquals('500', $response->getStatusCode()); + $this->assertEquals('foo', $response->getContent()); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithANonHandlingListener() + { + $exception = new \RuntimeException(); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + // should set a response, but does not + }); + + $kernel = $this->getHttpKernel($dispatcher, function () use ($exception) { throw $exception; }); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + $this->fail('LogicException expected'); + } catch (\RuntimeException $e) { + $this->assertSame($exception, $e); + } + } + + public function testHandleExceptionWithARedirectionResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new RedirectResponse('/login', 301)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new AccessDeniedHttpException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('301', $response->getStatusCode()); + $this->assertEquals('/login', $response->headers->get('Location')); + } + + public function testHandleHttpException() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new MethodNotAllowedHttpException(array('POST')); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + /** + * @group legacy + * @dataProvider getStatusCodes + */ + public function testLegacyHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) use ($responseStatusCode, $expectedStatusCode) { + $event->setResponse(new Response('', $responseStatusCode, array('X-Status-Code' => $expectedStatusCode))); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + $this->assertFalse($response->headers->has('X-Status-Code')); + } + + public function getStatusCodes() + { + return array( + array(200, 404), + array(404, 200), + array(301, 200), + array(500, 200), + ); + } + + /** + * @dataProvider getSpecificStatusCodes + */ + public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function (GetResponseForExceptionEvent $event) use ($expectedStatusCode) { + $event->allowCustomResponseCode(); + $event->setResponse(new Response('', $expectedStatusCode)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + } + + public function getSpecificStatusCodes() + { + return array( + array(200), + array(302), + array(403), + ); + } + + public function testHandleWhenAListenerReturnsAResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->setResponse(new Response('hello')); + }); + + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testHandleWhenNoControllerIsFound() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, false); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerIsAClosure() + { + $response = new Response('foo'); + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () use ($response) { return $response; }); + + $this->assertSame($response, $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnObjectWithInvoke() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, new Controller()); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAFunction() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, 'Symfony\Component\HttpKernel\Tests\controller_func'); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array(new Controller(), 'controller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAStaticArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array('Symfony\Component\HttpKernel\Tests\Controller', 'staticcontroller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + /** + * @expectedException \LogicException + */ + public function testHandleWhenTheControllerDoesNotReturnAResponse() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::VIEW, function ($event) { + $event->setResponse(new Response($event->getControllerResult())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleWithAResponseListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::RESPONSE, function ($event) { + $event->setResponse(new Response('foo')); + }); + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleAllowChangingControllerArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $event->setArguments(array('foo')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleAllowChangingControllerAndArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $oldController = $event->getController(); + $oldArguments = $event->getArguments(); + + $newController = function ($id) use ($oldController, $oldArguments) { + $response = call_user_func_array($oldController, $oldArguments); + + $response->headers->set('X-Id', $id); + + return $response; + }; + + $event->setController($newController); + $event->setArguments(array('bar')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }, null, array('foo')); + + $this->assertResponseEquals(new Response('foo', 200, array('X-Id' => 'bar')), $kernel->handle(new Request())); + } + + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher); + $dispatcher->addListener(KernelEvents::TERMINATE, function ($event) use (&$called, &$capturedKernel, &$capturedRequest, &$capturedResponse) { + $called = true; + $capturedKernel = $event->getKernel(); + $capturedRequest = $event->getRequest(); + $capturedResponse = $event->getResponse(); + }); + + $kernel->terminate($request = Request::create('/'), $response = new Response()); + $this->assertTrue($called); + $this->assertEquals($kernel, $capturedKernel); + $this->assertEquals($request, $capturedRequest); + $this->assertEquals($response, $capturedResponse); + } + + public function testVerifyRequestStackPushPopDuringHandle() + { + $request = new Request(); + + $stack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->setMethods(array('push', 'pop'))->getMock(); + $stack->expects($this->at(0))->method('push')->with($this->equalTo($request)); + $stack->expects($this->at(1))->method('pop'); + + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, null, $stack); + + $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testInconsistentClientIpsOnMasterRequests() + { + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->getRequest()->getClientIp(); + }); + + $kernel = $this->getHttpKernel($dispatcher); + $kernel->handle($request, $kernel::MASTER_REQUEST, false); + } + + private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = array()) + { + if (null === $controller) { + $controller = function () { return new Response('Hello'); }; + } + + $controllerResolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $controllerResolver + ->expects($this->any()) + ->method('getController') + ->will($this->returnValue($controller)); + + $argumentResolver = $this->getMockBuilder(ArgumentResolverInterface::class)->getMock(); + $argumentResolver + ->expects($this->any()) + ->method('getArguments') + ->will($this->returnValue($arguments)); + + return new HttpKernel($eventDispatcher, $controllerResolver, $requestStack, $argumentResolver); + } + + private function assertResponseEquals(Response $expected, Response $actual) + { + $expected->setDate($actual->getDate()); + $this->assertEquals($expected, $actual); + } +} + +class Controller +{ + public function __invoke() + { + return new Response('foo'); + } + + public function controller() + { + return new Response('foo'); + } + + public static function staticController() + { + return new Response('foo'); + } +} + +function controller_func() +{ + return new Response('foo'); +} diff --git a/vendor/symfony/http-kernel/Tests/KernelTest.php b/vendor/symfony/http-kernel/Tests/KernelTest.php new file mode 100644 index 0000000000..d2a27d03aa --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/KernelTest.php @@ -0,0 +1,900 @@ +<?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\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForOverrideName; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelWithoutBundles; + +class KernelTest extends TestCase +{ + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'getBundles')); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + // use test kernel to access isBooted() + $kernel = $this->getKernelForTest(array('initializeBundles', 'initializeContainer')); + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + /** + * @group legacy + */ + public function testClassCacheIsLoaded() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache('name', '.extension'); + $kernel->expects($this->once()) + ->method('doLoadClassCache') + ->with('name', '.extension'); + + $kernel->boot(); + } + + public function testClassCacheIsNotLoadedByDefault() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + + $kernel->boot(); + } + + /** + * @group legacy + */ + public function testClassCacheIsNotLoadedWhenKernelIsNotBooted() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache(); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + } + + public function testEnvParametersResourceIsAdded() + { + $container = new ContainerBuilder(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getContainerBuilder', 'prepareContainer', 'getCacheDir', 'getLogDir')) + ->getMock(); + $kernel->expects($this->any()) + ->method('getContainerBuilder') + ->will($this->returnValue($container)); + $kernel->expects($this->any()) + ->method('prepareContainer') + ->will($this->returnValue(null)); + $kernel->expects($this->any()) + ->method('getCacheDir') + ->will($this->returnValue(sys_get_temp_dir())); + $kernel->expects($this->any()) + ->method('getLogDir') + ->will($this->returnValue(sys_get_temp_dir())); + + $reflection = new \ReflectionClass(get_class($kernel)); + $method = $reflection->getMethod('buildContainer'); + $method->setAccessible(true); + $method->invoke($kernel); + + $found = false; + foreach ($container->getResources() as $resource) { + if ($resource instanceof EnvParametersResource) { + $found = true; + break; + } + } + + $this->assertTrue($found); + } + + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + + $kernel->boot(); + $kernel->boot(); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getKernel(array(), array($bundle)); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->at(3)) + ->method('setContainer') + ->with(null); + + $kernel = $this->getKernel(array('getBundles')); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getKernel(array('getHttpKernel', 'boot')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + $source = <<<'EOF' +<?php + +$string = 'string should not be modified'; + +$string = 'string should not be + +modified'; + + +$heredoc = <<<HD + + +Heredoc should not be modified {$a[1+$b]} + + +HD; + +$nowdoc = <<<'ND' + + +Nowdoc should not be modified + + +ND; + +/** + * some class comments to strip + */ +class TestClass +{ + /** + * some method comments to strip + */ + public function doStuff() + { + // inline comment + } +} +EOF; + $expected = <<<'EOF' +<?php +$string = 'string should not be modified'; +$string = 'string should not be + +modified'; +$heredoc = <<<HD + + +Heredoc should not be modified {$a[1+$b]} + + +HD; +$nowdoc = <<<'ND' + + +Nowdoc should not be modified + + +ND; +class TestClass +{ + public function doStuff() + { + } +} +EOF; + + $output = Kernel::stripComments($source); + + // Heredocs are preserved, making the output mixing Unix and Windows line + // endings, switching to "\n" everywhere on Windows to avoid failure. + if ('\\' === DIRECTORY_SEPARATOR) { + $expected = str_replace("\r\n", "\n", $expected); + $output = str_replace("\r\n", "\n", $output); + } + + $this->assertEquals($expected, $output); + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures', realpath($kernel->getRootDir())); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('Fixtures', $kernel->getName()); + } + + public function testOverrideGetName() + { + $kernel = new KernelForOverrideName('test', true); + + $this->assertEquals('overridden', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenNameIsNotValid() + { + $this->getKernel()->locateResource('Foo'); + } + + /** + * @expectedException \RuntimeException + */ + public function testLocateResourceThrowsExceptionWhenNameIsUnsafe() + { + $this->getKernel()->locateResource('@FooBundle/../bar'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenBundleDoesNotExist() + { + $this->getKernel()->locateResource('@FooBundle/config/routing.xml'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenResourceDoesNotExist() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $kernel->locateResource('@Bundle1Bundle/config/routing.xml'); + } + + public function testLocateResourceReturnsTheFirstThatMatches() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt', $kernel->locateResource('@Bundle1Bundle/foo.txt')); + } + + public function testLocateResourceReturnsTheFirstThatMatchesWithParent() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle2Bundle/foo.txt', $kernel->locateResource('@ParentAABundle/foo.txt')); + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/bar.txt', $kernel->locateResource('@ParentAABundle/bar.txt')); + } + + public function testLocateResourceReturnsAllMatches() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Bundle2Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false)); + } + + public function testLocateResourceReturnsAllMatchesBis() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array( + $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'), + $this->getBundle(__DIR__.'/Foobar'), + ))) + ; + + $this->assertEquals( + array(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false) + ); + } + + public function testLocateResourceIgnoresDirOnNonResource() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', + $kernel->locateResource('@Bundle1Bundle/foo.txt', __DIR__.'/Fixtures') + ); + } + + public function testLocateResourceReturnsTheDirOneForResources() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/foo.txt', + $kernel->locateResource('@FooBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + } + + public function testLocateResourceReturnsTheDirOneForResourcesAndBundleOnes() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/Bundle1Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/Resources/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + } + + public function testLocateResourceOverrideBundleAndResourcesFolders() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/BaseBundle', null, 'BaseBundle', 'BaseBundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/ChildBundle', 'ParentBundle', 'ChildBundle', 'ChildBundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(4)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + __DIR__.'/Fixtures/ChildBundle/Resources/foo.txt', + __DIR__.'/Fixtures/BaseBundle/Resources/foo.txt', + ), + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', false); + $this->fail('Hidden resources should raise an exception when returning an array of matching paths'); + } catch (\RuntimeException $e) { + } + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', true); + $this->fail('Hidden resources should raise an exception when returning the first matching path'); + } catch (\RuntimeException $e) { + } + } + + public function testLocateResourceOnDirectories() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/', + $kernel->locateResource('@FooBundle/Resources/', __DIR__.'/Fixtures/Resources') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle', + $kernel->locateResource('@FooBundle/Resources', __DIR__.'/Fixtures/Resources') + ); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources/', + $kernel->locateResource('@Bundle1Bundle/Resources/') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources', + $kernel->locateResource('@Bundle1Bundle/Resources') + ); + } + + public function testInitializeBundles() + { + $parent = $this->getBundle(null, null, 'ParentABundle'); + $child = $this->getBundle(null, 'ParentABundle', 'ChildABundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent), $map['ParentABundle']); + } + + public function testInitializeBundlesSupportInheritanceCascade() + { + $grandparent = $this->getBundle(null, null, 'GrandParentBBundle'); + $parent = $this->getBundle(null, 'GrandParentBBundle', 'ParentBBundle'); + $child = $this->getBundle(null, 'ParentBBundle', 'ChildBBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($grandparent, $parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentBBundle']); + $this->assertEquals(array($child, $parent), $map['ParentBBundle']); + $this->assertEquals(array($child), $map['ChildBBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ChildCBundle" extends bundle "FooBar", which is not registered. + */ + public function testInitializeBundlesThrowsExceptionWhenAParentDoesNotExists() + { + $child = $this->getBundle(null, 'FooBar', 'ChildCBundle'); + $kernel = $this->getKernel(array(), array($child)); + $kernel->boot(); + } + + public function testInitializeBundlesSupportsArbitraryBundleRegistrationOrder() + { + $grandparent = $this->getBundle(null, null, 'GrandParentCBundle'); + $parent = $this->getBundle(null, 'GrandParentCBundle', 'ParentCBundle'); + $child = $this->getBundle(null, 'ParentCBundle', 'ChildCBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $grandparent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentCBundle']); + $this->assertEquals(array($child, $parent), $map['ParentCBundle']); + $this->assertEquals(array($child), $map['ChildCBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ParentCBundle" is directly extended by two bundles "ChildC2Bundle" and "ChildC1Bundle". + */ + public function testInitializeBundlesThrowsExceptionWhenABundleIsDirectlyExtendedByTwoBundles() + { + $parent = $this->getBundle(null, null, 'ParentCBundle'); + $child1 = $this->getBundle(null, 'ParentCBundle', 'ChildC1Bundle'); + $child2 = $this->getBundle(null, 'ParentCBundle', 'ChildC2Bundle'); + + $kernel = $this->getKernel(array(), array($parent, $child1, $child2)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Trying to register two bundles with the same name "DuplicateName" + */ + public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWithTheSameName() + { + $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); + $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); + + $kernel = $this->getKernel(array(), array($fooBundle, $barBundle)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "CircularRefBundle" can not extend itself. + */ + public function testInitializeBundleThrowsExceptionWhenABundleExtendsItself() + { + $circularRef = $this->getBundle(null, 'CircularRefBundle', 'CircularRefBundle'); + + $kernel = $this->getKernel(array(), array($circularRef)); + $kernel->boot(); + } + + public function testTerminateReturnsSilentlyIfKernelIsNotBooted() + { + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->never()) + ->method('getHttpKernel'); + + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + // does not implement TerminableInterface + $httpKernel = new TestKernel(); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->willReturn($httpKernel); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + + $this->assertFalse($httpKernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate')) + ->getMock(); + + $httpKernelMock + ->expects($this->once()) + ->method('terminate'); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->exactly(2)) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testKernelWithoutBundles() + { + $kernel = new KernelWithoutBundles('test', true); + $kernel->boot(); + + $this->assertTrue($kernel->getContainer()->getParameter('test_executed')); + } + + public function testKernelRootDirNameStartingWithANumber() + { + $dir = __DIR__.'/Fixtures/123'; + require_once $dir.'/Kernel123.php'; + $kernel = new \Symfony\Component\HttpKernel\Tests\Fixtures\_123\Kernel123('dev', true); + $this->assertEquals('_123', $kernel->getName()); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\Kernel::getEnvParameters() method is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax to get the value of any environment variable from configuration files instead. + * @expectedDeprecation The support of special environment variables that start with SYMFONY__ (such as "SYMFONY__FOO__BAR") is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax instead to get the value of environment variables in configuration files. + */ + public function testSymfonyEnvironmentVariables() + { + $_SERVER['SYMFONY__FOO__BAR'] = 'baz'; + + $kernel = $this->getKernel(); + $method = new \ReflectionMethod($kernel, 'getEnvParameters'); + $method->setAccessible(true); + + $envParameters = $method->invoke($kernel); + $this->assertSame('baz', $envParameters['foo.bar']); + + unset($_SERVER['SYMFONY__FOO__BAR']); + } + + public function testProjectDirExtension() + { + $kernel = new CustomProjectDirKernel('test', true); + $kernel->boot(); + + $this->assertSame('foo', $kernel->getProjectDir()); + $this->assertSame('foo', $kernel->getContainer()->getParameter('kernel.project_dir')); + } + + /** + * Returns a mock for the BundleInterface. + * + * @return BundleInterface + */ + protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) + { + $bundle = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface') + ->setMethods(array('getPath', 'getParent', 'getName')) + ->disableOriginalConstructor() + ; + + if ($className) { + $bundle->setMockClassName($className); + } + + $bundle = $bundle->getMockForAbstractClass(); + + $bundle + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue(null === $bundleName ? get_class($bundle) : $bundleName)) + ; + + $bundle + ->expects($this->any()) + ->method('getPath') + ->will($this->returnValue($dir)) + ; + + $bundle + ->expects($this->any()) + ->method('getParent') + ->will($this->returnValue($parent)) + ; + + return $bundle; + } + + /** + * Returns a mock for the abstract kernel. + * + * @param array $methods Additional methods to mock (besides the abstract ones) + * @param array $bundles Bundles to register + * + * @return Kernel + */ + protected function getKernel(array $methods = array(), array $bundles = array()) + { + $methods[] = 'registerBundles'; + + $kernel = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Kernel') + ->setMethods($methods) + ->setConstructorArgs(array('test', false)) + ->getMockForAbstractClass() + ; + $kernel->expects($this->any()) + ->method('registerBundles') + ->will($this->returnValue($bundles)) + ; + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } + + protected function getKernelForTest(array $methods = array()) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->setConstructorArgs(array('test', false)) + ->setMethods($methods) + ->getMock(); + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate() + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} + +class CustomProjectDirKernel extends Kernel +{ + private $baseDir; + + public function __construct() + { + parent::__construct('test', false); + + $this->baseDir = 'foo'; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getProjectDir() + { + return $this->baseDir; + } + + public function getRootDir() + { + return __DIR__.'/Fixtures'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Logger.php b/vendor/symfony/http-kernel/Tests/Logger.php new file mode 100644 index 0000000000..63c70bf67a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Logger.php @@ -0,0 +1,88 @@ +<?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\Tests; + +use Psr\Log\LoggerInterface; + +class Logger implements LoggerInterface +{ + protected $logs; + + public function __construct() + { + $this->clear(); + } + + public function getLogs($level = false) + { + return false === $level ? $this->logs : $this->logs[$level]; + } + + public function clear() + { + $this->logs = array( + 'emergency' => array(), + 'alert' => array(), + 'critical' => array(), + 'error' => array(), + 'warning' => array(), + 'notice' => array(), + 'info' => array(), + 'debug' => array(), + ); + } + + public function log($level, $message, array $context = array()) + { + $this->logs[$level][] = $message; + } + + public function emergency($message, array $context = array()) + { + $this->log('emergency', $message, $context); + } + + public function alert($message, array $context = array()) + { + $this->log('alert', $message, $context); + } + + public function critical($message, array $context = array()) + { + $this->log('critical', $message, $context); + } + + public function error($message, array $context = array()) + { + $this->log('error', $message, $context); + } + + public function warning($message, array $context = array()) + { + $this->log('warning', $message, $context); + } + + public function notice($message, array $context = array()) + { + $this->log('notice', $message, $context); + } + + public function info($message, array $context = array()) + { + $this->log('info', $message, $context); + } + + public function debug($message, array $context = array()) + { + $this->log('debug', $message, $context); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php new file mode 100644 index 0000000000..99ff207567 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php @@ -0,0 +1,350 @@ +<?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\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class FileProfilerStorageTest extends TestCase +{ + private $tmpDir; + private $storage; + + protected function setUp() + { + $this->tmpDir = sys_get_temp_dir().'/sf2_profiler_file_storage'; + if (is_dir($this->tmpDir)) { + self::cleanDir(); + } + $this->storage = new FileProfilerStorage('file:'.$this->tmpDir); + $this->storage->purge(); + } + + protected function tearDown() + { + self::cleanDir(); + } + + public function testStore() + { + for ($i = 0; $i < 10; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(10, $this->storage->find('127.0.0.1', 'http://foo.bar', 20, 'GET'), '->write() stores data in the storage'); + } + + public function testChildren() + { + $parentProfile = new Profile('token_parent'); + $parentProfile->setIp('127.0.0.1'); + $parentProfile->setUrl('http://foo.bar/parent'); + + $childProfile = new Profile('token_child'); + $childProfile->setIp('127.0.0.1'); + $childProfile->setUrl('http://foo.bar/child'); + + $parentProfile->addChild($childProfile); + + $this->storage->write($parentProfile); + $this->storage->write($childProfile); + + // Load them from storage + $parentProfile = $this->storage->read('token_parent'); + $childProfile = $this->storage->read('token_child'); + + // Check child has link to parent + $this->assertNotNull($childProfile->getParent()); + $this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken()); + + // Check parent has child + $children = $parentProfile->getChildren(); + $this->assertCount(1, $children); + $this->assertEquals($childProfile->getToken(), $children[0]->getToken()); + } + + public function testStoreSpecialCharsInUrl() + { + // The storage accepts special characters in URLs (Even though URLs are not + // supposed to contain them) + $profile = new Profile('simple_quote'); + $profile->setUrl('http://foo.bar/\''); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('simple_quote'), '->write() accepts single quotes in URL'); + + $profile = new Profile('double_quote'); + $profile->setUrl('http://foo.bar/"'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('double_quote'), '->write() accepts double quotes in URL'); + + $profile = new Profile('backslash'); + $profile->setUrl('http://foo.bar/\\'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('backslash'), '->write() accepts backslash in URL'); + + $profile = new Profile('comma'); + $profile->setUrl('http://foo.bar/,'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('comma'), '->write() accepts comma in URL'); + } + + public function testStoreDuplicateToken() + { + $profile = new Profile('token'); + $profile->setUrl('http://example.com/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is unique'); + + $profile->setUrl('http://example.net/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is already present in the storage'); + $this->assertEquals('http://example.net/', $this->storage->read('token')->getUrl(), '->write() overwrites the current profile data'); + + $this->assertCount(1, $this->storage->find('', '', 1000, ''), '->find() does not return the same profile twice'); + } + + public function testRetrieveByIp() + { + $profile = new Profile('token'); + $profile->setIp('127.0.0.1'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->find() retrieve a record by IP'); + $this->assertCount(0, $this->storage->find('127.0.%.1', '', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the IP'); + $this->assertCount(0, $this->storage->find('127.0._.1', '', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the IP'); + } + + public function testRetrieveByStatusCode() + { + $profile200 = new Profile('statuscode200'); + $profile200->setStatusCode(200); + $this->storage->write($profile200); + + $profile404 = new Profile('statuscode404'); + $profile404->setStatusCode(404); + $this->storage->write($profile404); + + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '200'), '->find() retrieve a record by Status code 200'); + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '404'), '->find() retrieve a record by Status code 404'); + } + + public function testRetrieveByUrl() + { + $profile = new Profile('simple_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/\''); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('double_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/"'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('backslash'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo\\bar/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('percent'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/%'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('underscore'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/_'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('semicolon'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/;'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET'), '->find() accepts single quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET'), '->find() accepts double quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET'), '->find() accepts backslash in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET'), '->find() accepts semicolon in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the URL'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the URL'); + } + + public function testStoreTime() + { + $dt = new \DateTime('now'); + $start = $dt->getTimestamp(); + + for ($i = 0; $i < 3; ++$i) { + $dt->modify('+1 minute'); + $profile = new Profile('time_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setTime($dt->getTimestamp()); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 3 * 60); + $this->assertCount(3, $records, '->find() returns all previously added records'); + $this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order'); + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 2 * 60); + $this->assertCount(2, $records, '->find() should return only first two of the previously added records'); + } + + public function testRetrieveByEmptyUrlAndIp() + { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(5, $this->storage->find('', '', 10, 'GET'), '->find() returns all previously added records'); + $this->storage->purge(); + } + + public function testRetrieveByMethodAndLimit() + { + foreach (array('POST', 'GET') as $method) { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i.$method); + $profile->setMethod($method); + $this->storage->write($profile); + } + } + + $this->assertCount(5, $this->storage->find('', '', 5, 'POST')); + + $this->storage->purge(); + } + + public function testPurge() + { + $profile = new Profile('token1'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.com/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token1')); + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $profile = new Profile('token2'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token2')); + $this->assertCount(2, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $this->storage->purge(); + + $this->assertEmpty($this->storage->read('token'), '->purge() removes all data stored by profiler'); + $this->assertCount(0, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->purge() removes all items from index'); + } + + public function testDuplicates() + { + for ($i = 1; $i <= 5; ++$i) { + $profile = new Profile('foo'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + + ///three duplicates + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + $this->assertCount(3, $this->storage->find('127.0.0.1', 'http://example.net/', 3, 'GET'), '->find() method returns incorrect number of entries'); + } + + public function testStatusCode() + { + $profile = new Profile('token1'); + $profile->setStatusCode(200); + $this->storage->write($profile); + + $profile = new Profile('token2'); + $profile->setStatusCode(404); + $this->storage->write($profile); + + $tokens = $this->storage->find('', '', 10, ''); + $this->assertCount(2, $tokens); + $this->assertContains($tokens[0]['status_code'], array(200, 404)); + $this->assertContains($tokens[1]['status_code'], array(200, 404)); + } + + public function testMultiRowIndexFile() + { + $iteration = 3; + for ($i = 0; $i < $iteration; ++$i) { + $profile = new Profile('token'.$i); + $profile->setIp('127.0.0.'.$i); + $profile->setUrl('http://foo.bar/'.$i); + + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + + $handle = fopen($this->tmpDir.'/index.csv', 'r'); + for ($i = 0; $i < $iteration; ++$i) { + $row = fgetcsv($handle); + $this->assertEquals('token'.$i, $row[0]); + $this->assertEquals('127.0.0.'.$i, $row[1]); + $this->assertEquals('http://foo.bar/'.$i, $row[3]); + } + $this->assertFalse(fgetcsv($handle)); + } + + public function testReadLineFromFile() + { + $r = new \ReflectionMethod($this->storage, 'readLineFromFile'); + + $r->setAccessible(true); + + $h = tmpfile(); + + fwrite($h, "line1\n\n\nline2\n"); + fseek($h, 0, SEEK_END); + + $this->assertEquals('line2', $r->invoke($this->storage, $h)); + $this->assertEquals('line1', $r->invoke($this->storage, $h)); + } + + protected function cleanDir() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->tmpDir, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php new file mode 100644 index 0000000000..1a6f54636a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php @@ -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\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ProfilerTest extends TestCase +{ + private $tmp; + private $storage; + + public function testCollect() + { + $request = new Request(); + $request->query->set('foo', 'bar'); + $response = new Response('', 204); + $collector = new RequestDataCollector(); + + $profiler = new Profiler($this->storage); + $profiler->add($collector); + $profile = $profiler->collect($request, $response); + $profiler->saveProfile($profile); + + $this->assertSame(204, $profile->getStatusCode()); + $this->assertSame('GET', $profile->getMethod()); + $this->assertSame('bar', $profile->getCollector('request')->getRequestQuery()->all()['foo']->getValue()); + } + + public function testFindWorksWithDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '7th April 2014', '9th April 2014')); + } + + public function testFindWorksWithTimestamps() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '1396828800', '1397001600')); + } + + public function testFindWorksWithInvalidDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, 'some string', '')); + } + + public function testFindWorksWithStatusCode() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, null, null, '204')); + } + + protected function setUp() + { + $this->tmp = tempnam(sys_get_temp_dir(), 'sf2_profiler'); + if (file_exists($this->tmp)) { + @unlink($this->tmp); + } + + $this->storage = new FileProfilerStorage('file:'.$this->tmp); + $this->storage->purge(); + } + + protected function tearDown() + { + if (null !== $this->storage) { + $this->storage->purge(); + $this->storage = null; + + @unlink($this->tmp); + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php new file mode 100644 index 0000000000..3ec5927254 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + public function __construct() + { + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + return new Response('Request: '.$request->getRequestUri()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/UriSignerTest.php b/vendor/symfony/http-kernel/Tests/UriSignerTest.php new file mode 100644 index 0000000000..253a4c91bd --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/UriSignerTest.php @@ -0,0 +1,64 @@ +<?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\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends TestCase +{ + public function testSign() + { + $signer = new UriSigner('foobar'); + + $this->assertContains('?_hash=', $signer->sign('http://example.com/foo')); + $this->assertContains('&_hash=', $signer->sign('http://example.com/foo?foo=bar')); + } + + public function testCheck() + { + $signer = new UriSigner('foobar'); + + $this->assertFalse($signer->check('http://example.com/foo?_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo')); + + $this->assertTrue($signer->check($signer->sign('http://example.com/foo'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer'))); + + $this->assertTrue($signer->sign('http://example.com/foo?foo=bar&bar=foo') === $signer->sign('http://example.com/foo?bar=foo&foo=bar')); + } + + public function testCheckWithDifferentArgSeparator() + { + $this->iniSet('arg_separator.output', '&'); + $signer = new UriSigner('foobar'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } + + public function testCheckWithDifferentParameter() + { + $signer = new UriSigner('foobar', 'qux'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } +} diff --git a/vendor/symfony/http-kernel/UriSigner.php b/vendor/symfony/http-kernel/UriSigner.php new file mode 100644 index 0000000000..6f4f8865e2 --- /dev/null +++ b/vendor/symfony/http-kernel/UriSigner.php @@ -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; + +/** + * Signs URIs. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class UriSigner +{ + private $secret; + private $parameter; + + /** + * Constructor. + * + * @param string $secret A secret + * @param string $parameter Query string parameter to use + */ + public function __construct($secret, $parameter = '_hash') + { + $this->secret = $secret; + $this->parameter = $parameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + $uri = $this->buildUrl($url, $params); + + return $uri.(false === strpos($uri, '?') ? '?' : '&').$this->parameter.'='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * The query string parameter must be the last one + * (as it is generated that way by the sign() method, it should + * never be a problem). + * + * @param string $uri A signed URI + * + * @return bool True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + if (empty($params[$this->parameter])) { + return false; + } + + $hash = urlencode($params[$this->parameter]); + unset($params[$this->parameter]); + + return $this->computeHash($this->buildUrl($url, $params)) === $hash; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true))); + } + + private function buildUrl(array $url, array $params = array()) + { + ksort($params, SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = isset($url['host']) ? $url['host'] : ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = isset($url['user']) ? $url['user'] : ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($url['path']) ? $url['path'] : ''; + $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} diff --git a/vendor/symfony/http-kernel/composer.json b/vendor/symfony/http-kernel/composer.json new file mode 100644 index 0000000000..babc8663b6 --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,70 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Symfony HttpKernel Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3", + "symfony/debug": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3", + "psr/cache": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/http-kernel/phpunit.xml.dist b/vendor/symfony/http-kernel/phpunit.xml.dist new file mode 100644 index 0000000000..e0de769dd7 --- /dev/null +++ b/vendor/symfony/http-kernel/phpunit.xml.dist @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd" + backupGlobals="false" + colors="true" + bootstrap="vendor/autoload.php" + failOnRisky="true" + failOnWarning="true" +> + <php> + <ini name="error_reporting" value="-1" /> + </php> + + <testsuites> + <testsuite name="Symfony HttpKernel Component Test Suite"> + <directory>./Tests/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>./</directory> + <exclude> + <directory>./Tests</directory> + <directory>./vendor</directory> + </exclude> + </whitelist> + </filter> + + <listeners> + <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"> + <arguments> + <array> + <element key="time-sensitive"><string>Symfony\Component\HttpFoundation</string></element> + </array> + </arguments> + </listener> + </listeners> +</phpunit> |
