Merge branch '3.4'

* 3.4: (22 commits)
  use Precise on Travis to keep PHP LDAP support
  Fix case sensitive sameSite cookie
  [PropertyInfo] Use rawurlencode to escape PSR-6 keys
  fix(security): ensure the 'route' index is set before attempting to use it
  Fix registering lazy command services with autoconfigure enabled
  Fix the design of the profiler exceptions when there is no message
  [Config] Minor fix
  document the TwigRenderer class deprecation
  [Security] added more tests
  [Security] fixed default target path when referer contains a query string
  [Security] simplified tests
  [Security] refactored tests
  [WebProfilerBundle][TwigBundle] Fix infinite js loop on exception pages
  [FrameworkBundle] fix ValidatorCacheWarmer: use serializing ArrayAdapter
  Change "this" to "that" to avoid confusion
  [VarDumper] Move locale sniffing to dump() time
  [VarDumper] Use "C" locale when using "comma" flags
  [Config] Make ClassExistenceResource throw on invalid parents
  [DebugBundle] Added min_depth to Configuration
  [Console] Add a factory command loader for standalone application with lazy-loading needs
  ...
This commit is contained in:
Nicolas Grekas 2017-07-21 13:08:07 +02:00
commit d024d79559
39 changed files with 621 additions and 440 deletions

View File

@ -1,5 +1,6 @@
language: php
dist: precise
sudo: false
git:

View File

@ -77,6 +77,12 @@ SecurityBundle
* `FirewallContext::getListeners()` now returns `\Traversable|array`
TwigBridge
----------
* deprecated the `Symfony\Bridge\Twig\Form\TwigRenderer` class, use the `FormRenderer`
class from the Form component instead
Validator
---------

View File

@ -520,6 +520,9 @@ TwigBundle
TwigBridge
----------
* removed the `Symfony\Bridge\Twig\Form\TwigRenderer` class, use the `FormRenderer`
class from the Form component instead
* Removed the possibility to inject the Form `TwigRenderer` into the `FormExtension`.
Upgrade Twig to `^1.30`, inject the `Twig_Environment` into the `TwigRendererEngine` and load
the `TwigRenderer` using the `Twig_FactoryRuntimeLoader` instead.

View File

@ -36,6 +36,11 @@ class Configuration implements ConfigurationInterface
->min(-1)
->defaultValue(2500)
->end()
->integerNode('min_depth')
->info('Minimum tree depth to clone all the items, 1 is default')
->min(0)
->defaultValue(1)
->end()
->integerNode('max_string_length')
->info('Max length of displayed strings, -1 means no limit')
->min(-1)

View File

@ -37,6 +37,7 @@ class DebugExtension extends Extension
$container->getDefinition('var_dumper.cloner')
->addMethodCall('setMaxItems', array($config['max_items']))
->addMethodCall('setMinDepth', array($config['min_depth']))
->addMethodCall('setMaxString', array($config['max_string_length']));
if (null !== $config['dump_destination']) {

View File

@ -8,6 +8,7 @@
<xsd:complexType name="config">
<xsd:attribute name="max-items" type="xsd:integer" />
<xsd:attribute name="min-depth" type="xsd:integer" />
<xsd:attribute name="max-string-length" type="xsd:integer" />
<xsd:attribute name="dump-destination" type="xsd:string" />
</xsd:complexType>

View File

@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
/**
* @internal
*/
abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
{
private $phpArrayFile;
private $fallbackPool;
/**
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
*/
public function __construct($phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
$arrayAdapter = new ArrayAdapter();
spl_autoload_register(array(PhpArrayAdapter::class, 'throwOnRequiredClass'));
try {
if (!$this->doWarmUp($cacheDir, $arrayAdapter)) {
return;
}
} finally {
spl_autoload_unregister(array(PhpArrayAdapter::class, 'throwOnRequiredClass'));
}
// the ArrayAdapter stores the values serialized
// to avoid mutation of the data after it was written to the cache
// so here we un-serialize the values first
$values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues());
$this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool), $values);
foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
}
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
{
$phpArrayAdapter->warmUp($values);
}
/**
* @param string $cacheDir
* @param ArrayAdapter $arrayAdapter
*
* @return bool false if there is nothing to warm-up
*/
abstract protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter);
}

View File

@ -15,12 +15,8 @@ use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\Reader;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Cache\DoctrineProvider;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
/**
* Warms up annotation caches for classes found in composer's autoload class map
@ -28,11 +24,9 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class AnnotationsCacheWarmer implements CacheWarmerInterface
class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $annotationReader;
private $phpArrayFile;
private $fallbackPool;
/**
* @param Reader $annotationReader
@ -41,70 +35,41 @@ class AnnotationsCacheWarmer implements CacheWarmerInterface
*/
public function __construct(Reader $annotationReader, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
parent::__construct($phpArrayFile, $fallbackPool);
$this->annotationReader = $annotationReader;
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
{
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
$annotatedClassPatterns = $cacheDir.'/annotations.map';
if (!is_file($annotatedClassPatterns)) {
$adapter->warmUp(array());
return;
return true;
}
$annotatedClasses = include $annotatedClassPatterns;
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayAdapter));
$arrayPool = new ArrayAdapter(0, false);
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayPool));
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($annotatedClasses as $class) {
try {
$this->readAllComponents($reader, $class);
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
/*
* Ignore any AnnotationException to not break the cache warming process if an Annotation is badly
* configured or could not be found / read / etc.
*
* In particular cases, an Annotation in your code can be used and defined only for a specific
* environment but is always added to the annotations.map file by some Symfony default behaviors,
* and you always end up with a not found Annotation.
*/
}
foreach ($annotatedClasses as $class) {
try {
$this->readAllComponents($reader, $class);
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
/*
* Ignore any AnnotationException to not break the cache warming process if an Annotation is badly
* configured or could not be found / read / etc.
*
* In particular cases, an Annotation in your code can be used and defined only for a specific
* environment but is always added to the annotations.map file by some Symfony default behaviors,
* and you always end up with a not found Annotation.
*/
}
} finally {
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}
$values = $arrayPool->getValues();
$adapter->warmUp($values);
foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}

View File

@ -13,11 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
@ -30,11 +26,9 @@ use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class SerializerCacheWarmer implements CacheWarmerInterface
class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $loaders;
private $phpArrayFile;
private $fallbackPool;
/**
* @param LoaderInterface[] $loaders The serializer metadata loaders
@ -43,60 +37,33 @@ class SerializerCacheWarmer implements CacheWarmerInterface
*/
public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
parent::__construct($phpArrayFile, $fallbackPool);
$this->loaders = $loaders;
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
{
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
return;
return false;
}
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
$arrayPool = new ArrayAdapter(0, false);
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter);
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayPool);
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
$metadataFactory->getMetadataFor($mappedClass);
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
}
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
$metadataFactory->getMetadataFor($mappedClass);
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
}
}
} finally {
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}
$values = $arrayPool->getValues();
$adapter->warmUp($values);
foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}

View File

@ -13,11 +13,8 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
@ -31,11 +28,9 @@ use Symfony\Component\Validator\ValidatorBuilderInterface;
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class ValidatorCacheWarmer implements CacheWarmerInterface
class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $validatorBuilder;
private $phpArrayFile;
private $fallbackPool;
/**
* @param ValidatorBuilderInterface $validatorBuilder
@ -44,64 +39,43 @@ class ValidatorCacheWarmer implements CacheWarmerInterface
*/
public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
parent::__construct($phpArrayFile, $fallbackPool);
$this->validatorBuilder = $validatorBuilder;
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
{
if (!method_exists($this->validatorBuilder, 'getLoaders')) {
return;
return false;
}
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
$arrayPool = new ArrayAdapter(0, false);
$loaders = $this->validatorBuilder->getLoaders();
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayPool));
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayAdapter));
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
if ($metadataFactory->hasMetadataFor($mappedClass)) {
$metadataFactory->getMetadataFor($mappedClass);
}
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
if ($metadataFactory->hasMetadataFor($mappedClass)) {
$metadataFactory->getMetadataFor($mappedClass);
}
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
}
}
} finally {
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}
$values = $arrayPool->getValues();
$adapter->warmUp(array_filter($values));
foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
return true;
}
/**
* {@inheritdoc}
*/
public function isOptional()
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
{
return true;
// make sure we don't cache null values
parent::warmUpPhpArrayAdapter($phpArrayAdapter, array_filter($values));
}
/**

View File

@ -1,4 +1,4 @@
<div class="exception-summary">
<div class="exception-summary {{ exception.message is empty ? 'exception-without-message' }}">
<div class="exception-metadata">
<div class="container">
<h2 class="exception-hierarchy">
@ -13,9 +13,9 @@
</h2>
</div>
</div>
{% if exception.message is not empty %}
<div class="container">
<div class="exception-message-wrapper">
<div class="exception-message-wrapper">
<div class="container">
<h1 class="break-long-words exception-message {{ exception.message|length > 180 ? 'long' }}">
{{- exception.message|nl2br|format_file_from_text -}}
</h1>
@ -25,7 +25,6 @@
</div>
</div>
</div>
{% endif %}
</div>
<div class="container">

View File

@ -512,14 +512,14 @@
var altContent = toggle.getAttribute('data-toggle-alt-content');
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
});
}
/* Prevents from disallowing clicks on links inside toggles */
var toggleLinks = document.querySelectorAll('.sf-toggle a');
for (var i = 0; i < toggleLinks.length; i++) {
addEventListener(toggleLinks[i], 'click', function(e) {
e.stopPropagation();
});
}
/* Prevents from disallowing clicks on links inside toggles */
var toggleLinks = document.querySelectorAll('.sf-toggle a');
for (var i = 0; i < toggleLinks.length; i++) {
addEventListener(toggleLinks[i], 'click', function(e) {
e.stopPropagation();
});
}
}
};

View File

@ -79,7 +79,8 @@ header .container { display: flex; justify-content: space-between; }
.exception-hierarchy .icon { margin: 0 3px; opacity: .7; }
.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; }
.exception-message-wrapper { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 0 8px; }
.exception-without-message .exception-message-wrapper { display: none; }
.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 0 8px; }
.exception-message { flex-grow: 1; }
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
.exception-message.long { font-size: 18px; }

View File

@ -16,6 +16,9 @@
margin: 1em 0;
padding: 10px;
}
.exception-summary.exception-without-message {
display: none;
}
.exception-message {
color: #B0413E;
@ -26,6 +29,6 @@
display: none;
}
.exception-message-wrapper {
.exception-message-wrapper .container {
min-height: auto;
}

View File

@ -380,13 +380,12 @@
100% { background: #222; }
}
.sf-toolbar-block.sf-toolbar-block-dump {
position: static;
}
.sf-toolbar-block.sf-toolbar-block-dump .sf-toolbar-info {
max-width: none;
right: 0;
width: 100%;
position: fixed;
box-sizing: border-box;
left: 0;
}
.sf-toolbar-block-dump pre.sf-dump {

View File

@ -25,6 +25,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
private $exists;
private static $autoloadLevel = 0;
private static $autoloadedClass;
private static $existsCache = array();
/**
@ -57,6 +58,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
/**
* {@inheritdoc}
*
* @throws \ReflectionException when a parent class/interface/trait is not found
*/
public function isFresh($timestamp)
{
@ -68,12 +71,13 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}
$autoloadedClass = self::$autoloadedClass;
self::$autoloadedClass = $this->resource;
try {
$exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} catch (\ReflectionException $e) {
$exists = false;
} finally {
self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) {
spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
}
@ -112,7 +116,10 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
*/
private static function throwOnRequiredClass($class)
{
$e = new \ReflectionException("Class $class does not exist");
if (self::$autoloadedClass === $class) {
return;
}
$e = new \ReflectionException("Class $class not found");
$trace = $e->getTrace();
$autoloadFrame = array(
'function' => 'spl_autoload_call',
@ -138,6 +145,18 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
case 'is_callable':
return;
}
$props = array(
'file' => $trace[$i]['file'],
'line' => $trace[$i]['line'],
'trace' => array_slice($trace, 1 + $i),
);
foreach ($props as $p => $v) {
$r = new \ReflectionProperty('Exception', $p);
$r->setAccessible(true);
$r->setValue($e, $v);
}
}
throw $e;

View File

@ -14,7 +14,8 @@ CHANGELOG
3.4.0
-----
* added `CommandLoaderInterface` and PSR-11 `ContainerCommandLoader`
* added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
`ContainerCommandLoader` for commands lazy-loading
3.3.0
-----

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->factories);
}
}

View File

@ -70,20 +70,15 @@ class AddConsoleCommandPass implements CompilerPassInterface
$serviceIds[$commandId] = false;
$commandName = $tags[0]['command'];
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = array();
foreach ($tags as $tag) {
if (!isset($tag['command'])) {
throw new InvalidArgumentException(sprintf('Missing "command" attribute on tag "%s" for service "%s".', $this->commandTag, $id));
}
if ($commandName !== $tag['command']) {
throw new InvalidArgumentException(sprintf('The "command" attribute must be the same on each "%s" tag for service "%s".', $this->commandTag, $id));
}
if (isset($tag['alias'])) {
$aliases[] = $tag['alias'];
$lazyCommandMap[$tag['alias']] = $id;
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
}

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Console\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
@ -34,7 +34,6 @@ use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
class ApplicationTest extends TestCase
@ -128,10 +127,9 @@ class ApplicationTest extends TestCase
$commands = $application->all('foo');
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
$application->setCommandLoader(new ContainerCommandLoader(
new ServiceLocator(array('foo-bar' => function () { return new \Foo1Command(); })),
array('foo:bar1' => 'foo-bar')
));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar1' => function () { return new \Foo1Command(); },
)));
$commands = $application->all('foo');
$this->assertCount(2, $commands, '->all() takes a namespace as its first argument');
$this->assertInstanceOf(\FooCommand::class, $commands['foo:bar'], '->all() returns the registered commands');
@ -201,9 +199,9 @@ class ApplicationTest extends TestCase
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
$this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
'foo-bar' => function () { return new \Foo1Command(); },
)), array('foo:bar1' => 'foo-bar', 'afoobar1' => 'foo-bar')));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar1' => function () { return new \Foo1Command(); },
)));
$this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader');
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns an instance by name even with command loader');
@ -320,9 +318,9 @@ class ApplicationTest extends TestCase
public function testFindWithCommandLoader()
{
$application = new Application();
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
'foo-bar' => $f = function () { return new \FooCommand(); },
)), array('foo:bar' => 'foo-bar')));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar' => $f = function () { return new \FooCommand(); },
)));
$this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');
@ -1382,8 +1380,9 @@ class ApplicationTest extends TestCase
$container->addCompilerPass(new AddConsoleCommandPass());
$container
->register('lazy-command', LazyCommand::class)
->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias'))
->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias2'));
->addTag('console.command', array('command' => 'lazy:command'))
->addTag('console.command', array('command' => 'lazy:alias'))
->addTag('console.command', array('command' => 'lazy:alias2'));
$container->compile();
$application = new Application();

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tests\CommandLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
class FactoryCommandLoaderTest extends TestCase
{
public function testHas()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertTrue($loader->has('foo'));
$this->assertTrue($loader->has('bar'));
$this->assertFalse($loader->has('baz'));
}
public function testGet()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertInstanceOf(Command::class, $loader->get('foo'));
$this->assertInstanceOf(Command::class, $loader->get('bar'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
*/
public function testGetUnknownCommandThrows()
{
(new FactoryCommandLoader(array()))->get('unknown');
}
public function testGetCommandNames()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertSame(array('foo', 'bar'), $loader->getNames());
}
}

View File

@ -56,13 +56,14 @@ class AddConsoleCommandPassTest extends TestCase
$this->assertSame(array($alias => $id), $container->getParameter('console.command.ids'));
}
public function testProcessRegisterLazyCommands()
public function testProcessRegistersLazyCommands()
{
$container = new ContainerBuilder();
$container
$command = $container
->register('my-command', MyCommand::class)
->setPublic(false)
->addTag('console.command', array('command' => 'my:command', 'alias' => 'my:alias'))
->addTag('console.command', array('command' => 'my:command'))
->addTag('console.command', array('command' => 'my:alias'))
;
(new AddConsoleCommandPass())->process($container);
@ -74,6 +75,7 @@ class AddConsoleCommandPassTest extends TestCase
$this->assertSame(array('my:command' => 'my-command', 'my:alias' => 'my-command'), $commandLoader->getArgument(1));
$this->assertEquals(array(array('my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class)))), $commandLocator->getArguments());
$this->assertSame(array('console.command.symfony_component_console_tests_dependencyinjection_mycommand' => false), $container->getParameter('console.command.ids'));
$this->assertSame(array(array('setName', array('my:command')), array('setAliases', array(array('my:alias')))), $command->getMethodCalls());
}
public function visibilityProvider()

View File

@ -97,8 +97,8 @@ class AutowirePass extends AbstractRecursivePass
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" does not exist.', $this->currentId, $value->getClass()));
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
return $value;
}
@ -342,7 +342,7 @@ class AutowirePass extends AbstractRecursivePass
return;
}
if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass())) {
if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) {
return;
}
@ -394,7 +394,7 @@ class AutowirePass extends AbstractRecursivePass
*/
private function createAutowiredDefinition($type)
{
if (!($typeHint = $this->container->getReflectionClass($type)) || !$typeHint->isInstantiable()) {
if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) {
return;
}
@ -428,8 +428,8 @@ class AutowirePass extends AbstractRecursivePass
private function createTypeNotFoundMessage(TypedReference $reference, $label)
{
if (!$r = $this->container->getReflectionClass($type = $reference->getType())) {
$message = sprintf('has type "%s" but this class does not exist.', $type);
if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
$message = sprintf('has type "%s" but this class cannot be loaded.', $type);
} else {
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $this->createTypeAlternatives($reference));

View File

@ -26,7 +26,7 @@ trait PriorityTaggedServiceTrait
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use this class.
* we should not use that class.
*
* @see https://bugs.php.net/bug.php?id=53710
* @see https://bugs.php.net/bug.php?id=60926

View File

@ -66,7 +66,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
$instanceofTags = array();
foreach ($conditionals as $interface => $instanceofDefs) {
if ($interface !== $class && (!$container->getReflectionClass($class))) {
if ($interface !== $class && (!$container->getReflectionClass($class, false))) {
continue;
}

View File

@ -320,12 +320,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* Retrieves the requested reflection class and registers it for resource tracking.
*
* @param string $class
* @param bool $throw
*
* @return \ReflectionClass|null
*
* @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true
*
* @final
*/
public function getReflectionClass($class)
public function getReflectionClass($class, $throw = true)
{
if (!$class = $this->getParameterBag()->resolveValue($class)) {
return;
@ -340,6 +343,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class);
}
} catch (\ReflectionException $e) {
if ($throw) {
throw $e;
}
$classReflector = false;
}

View File

@ -330,7 +330,7 @@ class AutowirePassTest extends TestCase
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.
*/
public function testClassNotFoundThrowsException()
{
@ -345,7 +345,7 @@ class AutowirePassTest extends TestCase
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class does not exist.
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class cannot be loaded.
*/
public function testParentClassNotFoundThrowsException()
{
@ -685,7 +685,7 @@ class AutowirePassTest extends TestCase
public function provideNotWireableCalls()
{
return array(
array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.'),
array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'),
array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'),
array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
);

View File

@ -126,6 +126,10 @@ class Cookie
$this->httpOnly = (bool) $httpOnly;
$this->raw = (bool) $raw;
if (null !== $sameSite) {
$sameSite = strtolower($sameSite);
}
if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) {
throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
}

View File

@ -211,4 +211,10 @@ class CookieTest extends TestCase
$cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure');
$this->assertFalse($cookie->isHttpOnly());
}
public function testSameSiteAttributeIsCaseInsensitive()
{
$cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax');
$this->assertEquals('lax', $cookie->getSameSite());
}
}

View File

@ -108,7 +108,8 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
return call_user_func_array(array($this->propertyInfoExtractor, $method), $arguments);
}
$key = $this->escape($method.'.'.$serializedArguments);
// Calling rawurlencode escapes special characters not allowed in PSR-6's keys
$key = rawurlencode($method.'.'.$serializedArguments);
if (array_key_exists($key, $this->arrayCache)) {
return $this->arrayCache[$key];
@ -126,29 +127,4 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
return $this->arrayCache[$key] = $value;
}
/**
* Escapes a key according to PSR-6.
*
* Replaces characters forbidden by PSR-6 and the _ char by the _ char followed by the ASCII
* code of the escaped char.
*
* @param string $key
*
* @return string
*/
private function escape($key)
{
return strtr($key, array(
'{' => '_123',
'}' => '_125',
'(' => '_40',
')' => '_41',
'/' => '_47',
'\\' => '_92',
'@' => '_64',
':' => '_58',
'_' => '_95',
));
}
}

View File

@ -61,29 +61,4 @@ class PropertyInfoCacheExtractorTest extends AbstractPropertyInfoExtractorTest
parent::testGetProperties();
parent::testGetProperties();
}
/**
* @dataProvider escapeDataProvider
*/
public function testEscape($toEscape, $expected)
{
$reflectionMethod = new \ReflectionMethod($this->propertyInfo, 'escape');
$reflectionMethod->setAccessible(true);
$this->assertSame($expected, $reflectionMethod->invoke($this->propertyInfo, $toEscape));
}
public function escapeDataProvider()
{
return array(
array('foo_bar', 'foo_95bar'),
array('foo_95bar', 'foo_9595bar'),
array('foo{bar}', 'foo_123bar_125'),
array('foo(bar)', 'foo_40bar_41'),
array('foo/bar', 'foo_47bar'),
array('foo\bar', 'foo_92bar'),
array('foo@bar', 'foo_64bar'),
array('foo:bar', 'foo_58bar'),
);
}
}

View File

@ -122,8 +122,14 @@ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandle
return $targetUrl;
}
if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && parse_url($targetUrl, PHP_URL_PATH) !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
return $targetUrl;
if ($this->options['use_referer']) {
$targetUrl = $request->headers->get('Referer');
if (false !== $pos = strpos($targetUrl, '?')) {
$targetUrl = substr($targetUrl, 0, $pos);
}
if ($targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
return $targetUrl;
}
}
return $this->options['default_target_path'];

View File

@ -108,7 +108,7 @@ class HttpUtils
$parameters = $this->urlMatcher->match($request->getPathInfo());
}
return $path === $parameters['_route'];
return isset($parameters['_route']) && $path === $parameters['_route'];
} catch (MethodNotAllowedException $e) {
return false;
} catch (ResourceNotFoundException $e) {

View File

@ -12,193 +12,92 @@
namespace Symfony\Component\Security\Http\Tests\Authentication;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\HttpUtils;
class DefaultAuthenticationSuccessHandlerTest extends TestCase
{
private $httpUtils = null;
private $request = null;
private $token = null;
protected function setUp()
/**
* @dataProvider getRequestRedirections
*/
public function testRequestRedirections(Request $request, $options, $redirectedUrl)
{
$this->httpUtils = $this->getMockBuilder('Symfony\Component\Security\Http\HttpUtils')->getMock();
$this->request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$this->request->headers = $this->getMockBuilder('Symfony\Component\HttpFoundation\HeaderBag')->getMock();
$this->token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
$urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock();
$urlGenerator->expects($this->any())->method('generate')->will($this->returnValue('http://localhost/login'));
$httpUtils = new HttpUtils($urlGenerator);
$token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
$handler = new DefaultAuthenticationSuccessHandler($httpUtils, $options);
if ($request->hasSession()) {
$handler->setProviderKey('admin');
}
$this->assertSame('http://localhost'.$redirectedUrl, $handler->onAuthenticationSuccess($request, $token)->getTargetUrl());
}
public function testRequestIsRedirected()
{
$response = $this->expectRedirectResponse('/');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testDefaultTargetPathCanBeForced()
{
$options = array(
'always_use_default_target_path' => true,
'default_target_path' => '/dashboard',
);
$response = $this->expectRedirectResponse('/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testTargetPathIsPassedWithRequest()
{
$this->request->expects($this->once())
->method('get')->with('_target_path')
->will($this->returnValue('/dashboard'));
$response = $this->expectRedirectResponse('/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testTargetPathIsPassedAsNestedParameterWithRequest()
{
$this->request->expects($this->once())
->method('get')->with('_target_path')
->will($this->returnValue(array('value' => '/dashboard')));
$response = $this->expectRedirectResponse('/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array('target_path_parameter' => '_target_path[value]'));
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testTargetPathParameterIsCustomised()
{
$options = array('target_path_parameter' => '_my_target_path');
$this->request->expects($this->once())
->method('get')->with('_my_target_path')
->will($this->returnValue('/dashboard'));
$response = $this->expectRedirectResponse('/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testTargetPathIsTakenFromTheSession()
public function getRequestRedirections()
{
$session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock();
$session->expects($this->once())
->method('get')->with('_security.admin.target_path')
->will($this->returnValue('/admin/dashboard'));
$session->expects($this->once())
->method('remove')->with('_security.admin.target_path');
$session->expects($this->once())->method('get')->with('_security.admin.target_path')->will($this->returnValue('/admin/dashboard'));
$session->expects($this->once())->method('remove')->with('_security.admin.target_path');
$requestWithSession = Request::create('/');
$requestWithSession->setSession($session);
$this->request->expects($this->any())
->method('getSession')
->will($this->returnValue($session));
$response = $this->expectRedirectResponse('/admin/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
$handler->setProviderKey('admin');
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testTargetPathIsPassedAsReferer()
{
$options = array('use_referer' => true);
$this->request->headers->expects($this->once())
->method('get')->with('Referer')
->will($this->returnValue('/dashboard'));
$response = $this->expectRedirectResponse('/dashboard');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testRefererHasToBeDifferentThanLoginUrl()
{
$options = array('use_referer' => true);
$this->request->headers->expects($this->any())
->method('get')->with('Referer')
->will($this->returnValue('/login'));
$this->httpUtils->expects($this->once())
->method('generateUri')->with($this->request, '/login')
->will($this->returnValue('/login'));
$response = $this->expectRedirectResponse('/');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testRefererWithoutParametersHasToBeDifferentThanLoginUrl()
{
$options = array('use_referer' => true);
$this->request->headers->expects($this->any())
->method('get')->with('Referer')
->will($this->returnValue('/subfolder/login?t=1&p=2'));
$this->httpUtils->expects($this->once())
->method('generateUri')->with($this->request, '/login')
->will($this->returnValue('/subfolder/login'));
$response = $this->expectRedirectResponse('/');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
public function testRefererTargetPathIsIgnoredByDefault()
{
$this->request->headers->expects($this->never())->method('get');
$response = $this->expectRedirectResponse('/');
$handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
$result = $handler->onAuthenticationSuccess($this->request, $this->token);
$this->assertSame($response, $result);
}
private function expectRedirectResponse($path)
{
$response = new Response();
$this->httpUtils->expects($this->once())
->method('createRedirectResponse')
->with($this->request, $path)
->will($this->returnValue($response));
return $response;
return array(
'default' => array(
Request::create('/'),
array(),
'/',
),
'forced target path' => array(
Request::create('/'),
array('always_use_default_target_path' => true, 'default_target_path' => '/dashboard'),
'/dashboard',
),
'target path as query string' => array(
Request::create('/?_target_path=/dashboard'),
array(),
'/dashboard',
),
'target path name as query string is customized' => array(
Request::create('/?_my_target_path=/dashboard'),
array('target_path_parameter' => '_my_target_path'),
'/dashboard',
),
'target path name as query string is customized and nested' => array(
Request::create('/?_target_path[value]=/dashboard'),
array('target_path_parameter' => '_target_path[value]'),
'/dashboard',
),
'target path in session' => array(
$requestWithSession,
array(),
'/admin/dashboard',
),
'target path as referer' => array(
Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/dashboard')),
array('use_referer' => true),
'/dashboard',
),
'target path as referer is ignored if not configured' => array(
Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/dashboard')),
array(),
'/',
),
'target path should be different than login URL' => array(
Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login')),
array('use_referer' => true, 'login_path' => '/login'),
'/',
),
'target path should be different than login URL (query string does not matter)' => array(
Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login?t=1&p=2')),
array('use_referer' => true, 'login_path' => '/login'),
'/',
),
'target path should be different than login URL (login_path as a route)' => array(
Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login?t=1&p=2')),
array('use_referer' => true, 'login_path' => 'login_route'),
'/',
),
);
}
}

View File

@ -221,6 +221,19 @@ class HttpUtilsTest extends TestCase
$utils->checkRequestPath($this->getRequest(), 'foobar');
}
public function testCheckPathWithoutRouteParam()
{
$urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock();
$urlMatcher
->expects($this->any())
->method('match')
->willReturn(array('_controller' => 'PathController'))
;
$utils = new HttpUtils(null, $urlMatcher);
$this->assertFalse($utils->checkRequestPath($this->getRequest(), 'path/index.html'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Matcher must either implement UrlMatcherInterface or RequestMatcherInterface

View File

@ -27,7 +27,7 @@ class DateCaster
$fromNow = (new \DateTime())->diff($d);
$title = $d->format('l, F j, Y')
."\n".$fromNow->format('%R').(ltrim($fromNow->format('%yy %mm %dd %H:%I:%Ss'), ' 0ymd:s') ?: '0s').' from now'
."\n".$fromNow->format('%R').self::formatInterval($fromNow).' from now'
.($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
;
@ -38,4 +38,34 @@ class DateCaster
return $a;
}
public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter)
{
$now = new \DateTimeImmutable();
$numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp();
$title = number_format($numberOfSeconds, 0, '.', ' ').'s';
$i = array(Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title));
return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
}
private static function formatInterval(\DateInterval $i)
{
$format = '%R '
.($i->y ? '%yy ' : '')
.($i->m ? '%mm ' : '')
.($i->d ? '%dd ' : '')
;
if (\PHP_VERSION_ID >= 70100 && isset($i->f)) {
$format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:%S.%F' : '';
} else {
$format .= $i->h || $i->i || $i->s ? '%H:%I:%S' : '';
}
$format = '%R ' === $format ? '0s' : $format;
return $i->format(rtrim($format));
}
}

View File

@ -110,6 +110,7 @@ abstract class AbstractCloner implements ClonerInterface
'RedisArray' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'),
'DateTimeInterface' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'),
'DateInterval' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'),
':curl' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'),
':dba' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'),

View File

@ -46,8 +46,8 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{
$this->flags = (int) $flags;
$this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
$this->decimalPoint = (string) 0.5;
$this->decimalPoint = $this->decimalPoint[1];
$this->decimalPoint = localeconv();
$this->decimalPoint = $this->decimalPoint['decimal_point'];
$this->setOutput($output ?: static::$defaultOutput);
if (!$output && is_string(static::$defaultOutput)) {
static::$defaultOutput = $this->outputStream;
@ -123,6 +123,13 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
*/
public function dump(Data $data, $output = null)
{
$this->decimalPoint = localeconv();
$this->decimalPoint = $this->decimalPoint['decimal_point'];
if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(LC_NUMERIC, 0) : null) {
setlocale(LC_NUMERIC, 'C');
}
if ($returnDump = true === $output) {
$output = fopen('php://memory', 'r+b');
}
@ -143,6 +150,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
if ($output) {
$this->setOutput($prevOutput);
}
if ($locale) {
setlocale(LC_NUMERIC, $locale);
}
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\DateCaster;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
@ -80,4 +81,103 @@ EODUMP;
array('2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.000000 +02:00'),
);
}
/**
* @dataProvider provideIntervals
*/
public function testDumpInterval($intervalSpec, $invert, $expected)
{
$interval = new \DateInterval($intervalSpec);
$interval->invert = $invert;
$xDump = <<<EODUMP
DateInterval {
interval: $expected
%A}
EODUMP;
$this->assertDumpMatchesFormat($xDump, $interval);
}
/**
* @dataProvider provideIntervals
*/
public function testDumpIntervalExcludingVerbosity($intervalSpec, $invert, $expected)
{
$interval = new \DateInterval($intervalSpec);
$interval->invert = $invert;
$xDump = <<<EODUMP
DateInterval {
interval: $expected
}
EODUMP;
$this->assertDumpMatchesFormat($xDump, $interval, Caster::EXCLUDE_VERBOSE);
}
/**
* @dataProvider provideIntervals
*/
public function testCastInterval($intervalSpec, $invert, $xInterval, $xSeconds)
{
$interval = new \DateInterval($intervalSpec);
$interval->invert = $invert;
$stub = new Stub();
$cast = DateCaster::castInterval($interval, array('foo' => 'bar'), $stub, false, Caster::EXCLUDE_VERBOSE);
$xDump = <<<EODUMP
array:1 [
"\\x00~\\x00interval" => $xInterval
]
EODUMP;
$this->assertDumpMatchesFormat($xDump, $cast);
if (null === $xSeconds) {
return;
}
$xDump = <<<EODUMP
Symfony\Component\VarDumper\Caster\ConstStub {
+type: "ref"
+class: "$xInterval"
+value: "$xSeconds"
+cut: 0
+handle: 0
+refCount: 0
+position: 0
+attr: []
}
EODUMP;
$this->assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]);
}
public function provideIntervals()
{
$i = new \DateInterval('PT0S');
$ms = \PHP_VERSION_ID >= 70100 && isset($i->f) ? '.000000' : '';
return array(
array('PT0S', 0, '0s', '0s'),
array('PT1S', 0, '+ 00:00:01'.$ms, '1s'),
array('PT2M', 0, '+ 00:02:00'.$ms, '120s'),
array('PT3H', 0, '+ 03:00:00'.$ms, '10 800s'),
array('P4D', 0, '+ 4d', '345 600s'),
array('P5M', 0, '+ 5m', null),
array('P6Y', 0, '+ 6y', null),
array('P1Y2M3DT4H5M6S', 0, '+ 1y 2m 3d 04:05:06'.$ms, null),
array('PT0S', 1, '0s', '0s'),
array('PT1S', 1, '- 00:00:01'.$ms, '-1s'),
array('PT2M', 1, '- 00:02:00'.$ms, '-120s'),
array('PT3H', 1, '- 03:00:00'.$ms, '-10 800s'),
array('P4D', 1, '- 4d', '-345 600s'),
array('P5M', 1, '- 5m', null),
array('P6Y', 1, '- 6y', null),
array('P1Y2M3DT4H5M6S', 1, '- 1y 2m 3d 04:05:06'.$ms, null),
);
}
}