diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 62313cb441..aa5fae01cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -683,7 +683,9 @@ class Configuration implements ConfigurationInterface ->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() - ->scalarNode('cache')->end() + ->scalarNode('cache') + ->setDeprecated('The "%path%.%node%" option is deprecated since Symfony 3.1 and will be removed in 4.0. Configure the "cache.serializer" service under "framework.cache.pools" instead.') + ->end() ->scalarNode('name_converter')->end() ->scalarNode('circular_reference_handler')->end() ->arrayNode('mapping') diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 1e0617ebe2..96df1128bb 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -14,11 +14,12 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; /** * @author Nicolas Grekas */ -class TagAwareAdapter implements TagAwareAdapterInterface +class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface { const TAGS_PREFIX = "\0tags\0"; @@ -334,4 +335,16 @@ class TagAwareAdapter implements TagAwareAdapterInterface return $tagVersions; } + + /** + * {@inheritdoc} + */ + public function prune() + { + if ($this->itemsAdapter instanceof PruneableInterface) { + return $this->itemsAdapter->prune(); + } + + return false; + } } diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index bd637f10df..7389a7edcc 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -5,7 +5,7 @@ CHANGELOG ----- * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning - * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, and ChainTrait + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and ChainCache implement PruneableInterface and support manual stale cache pruning diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php index deca227c47..0e4e07a16d 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; +use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; @@ -125,4 +126,60 @@ class TagAwareAdapterTest extends AdapterTestCase $i = $pool->getItem('k'); $this->assertSame(array('foo' => 'foo'), $i->getPreviousTags()); } + + public function testPrune() + { + $cache = new TagAwareAdapter($this->getPruneableMock()); + $this->assertTrue($cache->prune()); + + $cache = new TagAwareAdapter($this->getNonPruneableMock()); + $this->assertFalse($cache->prune()); + + $cache = new TagAwareAdapter($this->getFailingPruneableMock()); + $this->assertFalse($cache->prune()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(true)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getFailingPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(false)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|AdapterInterface + */ + private function getNonPruneableMock() + { + return $this + ->getMockBuilder(AdapterInterface::class) + ->getMock(); + } } diff --git a/src/Symfony/Component/Config/Definition/ArrayNode.php b/src/Symfony/Component/Config/Definition/ArrayNode.php index 2ffa2a2137..ef068c9f34 100644 --- a/src/Symfony/Component/Config/Definition/ArrayNode.php +++ b/src/Symfony/Component/Config/Definition/ArrayNode.php @@ -234,10 +234,6 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface } foreach ($this->children as $name => $child) { - if ($child->isDeprecated()) { - @trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED); - } - if (!array_key_exists($name, $value)) { if ($child->isRequired()) { $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()); @@ -254,6 +250,10 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface continue; } + if ($child->isDeprecated()) { + @trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED); + } + try { $value[$name] = $child->finalize($value[$name]); } catch (UnsetKeyException $e) { diff --git a/src/Symfony/Component/Config/Definition/VariableNode.php b/src/Symfony/Component/Config/Definition/VariableNode.php index f37ced901b..a9c35284cd 100644 --- a/src/Symfony/Component/Config/Definition/VariableNode.php +++ b/src/Symfony/Component/Config/Definition/VariableNode.php @@ -84,10 +84,6 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface */ protected function finalizeValue($value) { - if ($this->deprecationMessage) { - @trigger_error($this->getDeprecationMessage($this->getName(), $this->getPath()), E_USER_DEPRECATED); - } - if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { $ex = new InvalidConfigurationException(sprintf( 'The path "%s" cannot contain an empty value, but got %s.', diff --git a/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php b/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php index 7a5bf30373..58d2939300 100644 --- a/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php @@ -219,10 +219,33 @@ class ArrayNodeTest extends TestCase public function testSetDeprecated() { - $node = new ArrayNode('foo'); - $node->setDeprecated('"%node%" is deprecated'); + $childNode = new ArrayNode('foo'); + $childNode->setDeprecated('"%node%" is deprecated'); - $this->assertTrue($node->isDeprecated()); - $this->assertSame('"foo" is deprecated', $node->getDeprecationMessage($node->getName(), $node->getPath())); + $this->assertTrue($childNode->isDeprecated()); + $this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath())); + + $node = new ArrayNode('root'); + $node->addChild($childNode); + + $deprecationTriggered = false; + $deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) { + if (E_USER_DEPRECATED === $level) { + return $deprecationTriggered = true; + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }; + + $prevErrorHandler = set_error_handler($deprecationHandler); + $node->finalize(array()); + restore_error_handler(); + + $this->assertFalse($deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set'); + + $prevErrorHandler = set_error_handler($deprecationHandler); + $node->finalize(array('foo' => array())); + restore_error_handler(); + $this->assertTrue($deprecationTriggered, '->finalize() should trigger if the deprecated node is set'); } } diff --git a/src/Symfony/Component/Config/Tests/Definition/ScalarNodeTest.php b/src/Symfony/Component/Config/Tests/Definition/ScalarNodeTest.php index 1981ac9459..481ef3f496 100644 --- a/src/Symfony/Component/Config/Tests/Definition/ScalarNodeTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/ScalarNodeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Config\Tests\Definition; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\ScalarNode; class ScalarNodeTest extends TestCase @@ -42,11 +43,33 @@ class ScalarNodeTest extends TestCase public function testSetDeprecated() { - $node = new ScalarNode('foo'); - $node->setDeprecated('"%node%" is deprecated'); + $childNode = new ScalarNode('foo'); + $childNode->setDeprecated('"%node%" is deprecated'); - $this->assertTrue($node->isDeprecated()); - $this->assertSame('"foo" is deprecated', $node->getDeprecationMessage($node->getName(), $node->getPath())); + $this->assertTrue($childNode->isDeprecated()); + $this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath())); + + $node = new ArrayNode('root'); + $node->addChild($childNode); + + $deprecationTriggered = 0; + $deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) { + if (E_USER_DEPRECATED === $level) { + return ++$deprecationTriggered; + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }; + + $prevErrorHandler = set_error_handler($deprecationHandler); + $node->finalize(array()); + restore_error_handler(); + $this->assertSame(0, $deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set'); + + $prevErrorHandler = set_error_handler($deprecationHandler); + $node->finalize(array('foo' => '')); + restore_error_handler(); + $this->assertSame(1, $deprecationTriggered, '->finalize() should trigger if the deprecated node is set'); } /** diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php index b39b1f9b05..e2868ff718 100644 --- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php @@ -34,7 +34,7 @@ class DateCaster ; $a = array(); - $a[$prefix.'date'] = new ConstStub($d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).($location ? ' e (P)' : ' P')), $title); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); $stub->class .= $d->format(' @U'); @@ -63,7 +63,7 @@ class DateCaster $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); } - $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, $i->f) : ''; + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; $format = '%R ' === $format ? '0s' : $format; return $i->format(rtrim($format)); @@ -93,16 +93,16 @@ class DateCaster ); break; } - $dates[] = sprintf('%s) %s', $i + 1, $d->format('Y-m-d H:i:s')); + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); } } $period = sprintf( 'every %s, from %s (%s) %s', self::formatInterval($p->getDateInterval()), - $p->getStartDate()->format('Y-m-d H:i:s'), + self::formatDateTime($p->getStartDate()), $p->include_start_date ? 'included' : 'excluded', - ($end = $p->getEndDate()) ? 'to '.$end->format('Y-m-d H:i:s') : 'recurring '.$p->recurrences.' time/s' + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s' ); $p = array(Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))); @@ -110,6 +110,11 @@ class DateCaster return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; } + private static function formatDateTime(\DateTimeInterface $d, $extra = '') + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + private static function formatSeconds($s, $us) { return sprintf('%02d.%s', $s, 0 === ($len = strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php index a3bc4867a8..cb6b6a6b16 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php @@ -92,10 +92,9 @@ EODUMP; /** * @dataProvider provideIntervals */ - public function testDumpInterval($intervalSpec, $invert, $expected) + public function testDumpInterval($intervalSpec, $ms, $invert, $expected) { - $interval = new \DateInterval($intervalSpec); - $interval->invert = $invert; + $interval = $this->createInterval($intervalSpec, $ms, $invert); $xDump = <<invert = $invert; + $interval = $this->createInterval($intervalSpec, $ms, $invert); $xDump = <<invert = $invert; + $interval = $this->createInterval($intervalSpec, $ms, $invert); $stub = new Stub(); $cast = DateCaster::castInterval($interval, array('foo' => 'bar'), $stub, false, Caster::EXCLUDE_VERBOSE); @@ -159,37 +156,39 @@ Symfony\Component\VarDumper\Caster\ConstStub { } EODUMP; - $this->assertDumpEquals($xDump, $cast["\0~\0interval"]); + $this->assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]); } public function provideIntervals() { return array( - array('PT0S', 0, '0s', '0s'), - array('PT1S', 0, '+ 00:00:01.0', '1s'), - array('PT2M', 0, '+ 00:02:00.0', '120s'), - array('PT3H', 0, '+ 03:00:00.0', '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.0', null), - array('PT1M60S', 0, '+ 00:02:00.0', null), - array('PT1H60M', 0, '+ 02:00:00.0', null), - array('P1DT24H', 0, '+ 2d', null), - array('P1M32D', 0, '+ 1m 32d', null), + array('PT0S', 0, 0, '0s', '0s'), + array('PT0S', 0.1, 0, '+ 00:00:00.100', '%is'), + array('PT1S', 0, 0, '+ 00:00:01.0', '1s'), + array('PT2M', 0, 0, '+ 00:02:00.0', '120s'), + array('PT3H', 0, 0, '+ 03:00:00.0', '10 800s'), + array('P4D', 0, 0, '+ 4d', '345 600s'), + array('P5M', 0, 0, '+ 5m', null), + array('P6Y', 0, 0, '+ 6y', null), + array('P1Y2M3DT4H5M6S', 0, 0, '+ 1y 2m 3d 04:05:06.0', null), + array('PT1M60S', 0, 0, '+ 00:02:00.0', null), + array('PT1H60M', 0, 0, '+ 02:00:00.0', null), + array('P1DT24H', 0, 0, '+ 2d', null), + array('P1M32D', 0, 0, '+ 1m 32d', null), - array('PT0S', 1, '0s', '0s'), - array('PT1S', 1, '- 00:00:01.0', '-1s'), - array('PT2M', 1, '- 00:02:00.0', '-120s'), - array('PT3H', 1, '- 03:00:00.0', '-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.0', null), - array('PT1M60S', 1, '- 00:02:00.0', null), - array('PT1H60M', 1, '- 02:00:00.0', null), - array('P1DT24H', 1, '- 2d', null), - array('P1M32D', 1, '- 1m 32d', null), + array('PT0S', 0, 1, '0s', '0s'), + array('PT0S', 0.1, 1, '- 00:00:00.100', '%is'), + array('PT1S', 0, 1, '- 00:00:01.0', '-1s'), + array('PT2M', 0, 1, '- 00:02:00.0', '-120s'), + array('PT3H', 0, 1, '- 03:00:00.0', '-10 800s'), + array('P4D', 0, 1, '- 4d', '-345 600s'), + array('P5M', 0, 1, '- 5m', null), + array('P6Y', 0, 1, '- 6y', null), + array('P1Y2M3DT4H5M6S', 0, 1, '- 1y 2m 3d 04:05:06.0', null), + array('PT1M60S', 0, 1, '- 00:02:00.0', null), + array('PT1H60M', 0, 1, '- 02:00:00.0', null), + array('P1DT24H', 0, 1, '- 2d', null), + array('P1M32D', 0, 1, '- 1m 32d', null), ); } @@ -318,7 +317,7 @@ array:1 [ ] EODUMP; - $this->assertDumpMatchesFormat($xDump, $cast); + $this->assertDumpEquals($xDump, $cast); $xDump = <<f = $ms; + $interval->invert = $invert; + + return $interval; + } }