diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 6b9f7ed50ddf7..b6e493916b860 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -68,15 +68,13 @@ function ($key, $value, $isHit) use ($defaultLifetime) { $this->mergeByLifetime = \Closure::bind( function ($deferred, $namespace, &$expiredIds) use ($getId) { $byLifetime = array(); - $now = time(); + $now = microtime(true); $expiredIds = array(); foreach ($deferred as $key => $item) { if (null === $item->expiry) { $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0; - } elseif ($item->expiry > $now) { - $ttl = $item->expiry - $now; - } else { + } elseif (0 >= $ttl = (int) ($item->expiry - $now)) { $expiredIds[] = $getId($key); continue; } @@ -107,7 +105,7 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) { */ public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); if (null === self::$apcuSupported) { self::$apcuSupported = ApcuAdapter::isSupported(); diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index 17f2beaf0fe75..07fc198115a50 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -87,7 +87,7 @@ public function getItems(array $keys = array()) CacheItem::validateKey($key); } - return $this->generateItems($keys, time(), $this->createCacheItem); + return $this->generateItems($keys, microtime(true), $this->createCacheItem); } /** @@ -115,7 +115,7 @@ public function save(CacheItemInterface $item) $value = $item["\0*\0value"]; $expiry = $item["\0*\0expiry"]; - if (null !== $expiry && $expiry <= time()) { + if (null !== $expiry && $expiry <= microtime(true)) { $this->deleteItem($key); return true; @@ -131,7 +131,7 @@ public function save(CacheItemInterface $item) } } if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { - $expiry = time() + $item["\0*\0defaultLifetime"]; + $expiry = microtime(true) + $item["\0*\0defaultLifetime"]; } $this->values[$key] = $value; diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 796dd6c6063fc..ddb533e0a95d7 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -81,7 +81,7 @@ function (CacheItemInterface $innerItem, array $item) { $item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]); } $innerItem->set($item["\0*\0value"]); - $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U', $item["\0*\0expiry"]) : null); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null); }, null, CacheItem::class @@ -200,7 +200,7 @@ private function doSave(CacheItemInterface $item, $method) } $item = (array) $item; if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) { - $item["\0*\0expiry"] = time() + $item["\0*\0defaultLifetime"]; + $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"]; } $innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]); ($this->setInnerItem)($innerItem, $item); diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 9026c1c95adf3..2288db8cfebdb 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead * deprecated the `AbstractAdapter::createSystemCache()` method diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 91fdc53164040..d762539203df6 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -89,9 +89,9 @@ public function set($value) public function expiresAt($expiration) { if (null === $expiration) { - $this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null; + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; } elseif ($expiration instanceof \DateTimeInterface) { - $this->expiry = (int) $expiration->format('U'); + $this->expiry = (float) $expiration->format('U.u'); } else { throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', is_object($expiration) ? get_class($expiration) : gettype($expiration))); } @@ -105,11 +105,11 @@ public function expiresAt($expiration) public function expiresAfter($time) { if (null === $time) { - $this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null; + $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; } elseif ($time instanceof \DateInterval) { - $this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U'); + $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (\is_int($time)) { - $this->expiry = $time + time(); + $this->expiry = $time + microtime(true); } else { throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($time) ? get_class($time) : gettype($time))); } diff --git a/src/Symfony/Component/Cache/Simple/ArrayCache.php b/src/Symfony/Component/Cache/Simple/ArrayCache.php index d1ef583125b8b..6cef8b6e745c4 100644 --- a/src/Symfony/Component/Cache/Simple/ArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/ArrayCache.php @@ -64,7 +64,7 @@ public function getMultiple($keys, $default = null) CacheItem::validateKey($key); } - return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; }); + return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; }); } /** @@ -121,7 +121,7 @@ public function setMultiple($values, $ttl = null) } } } - $expiry = 0 < $ttl ? time() + $ttl : PHP_INT_MAX; + $expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX; foreach ($valuesArray as $key => $value) { $this->values[$key] = $value; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 385c70720901a..72d143e3c039c 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -77,10 +77,10 @@ public function testGetMetadata() $item = $cache->getItem('foo'); $expected = array( - CacheItem::METADATA_EXPIRY => 9 + time(), + CacheItem::METADATA_EXPIRY => 9.5 + time(), CacheItem::METADATA_CTIME => 1000, ); - $this->assertSame($expected, $item->getMetadata()); + $this->assertEquals($expected, $item->getMetadata(), 'Item metadata should embed expiry and ctime.', .6); } public function testDefaultLifeTime() diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php index 1a6157e822117..27fb82de0186e 100644 --- a/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php +++ b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php @@ -21,12 +21,12 @@ protected function doContains($id) $expiry = $this->data[$id][1]; - return !$expiry || time() <= $expiry || !$this->doDelete($id); + return !$expiry || microtime(true) < $expiry || !$this->doDelete($id); } protected function doSave($id, $data, $lifeTime = 0) { - $this->data[$id] = array($data, $lifeTime ? time() + $lifeTime : false); + $this->data[$id] = array($data, $lifeTime ? microtime(true) + $lifeTime : false); return true; } diff --git a/src/Symfony/Component/Cache/Traits/ArrayTrait.php b/src/Symfony/Component/Cache/Traits/ArrayTrait.php index b7d2ad6d6299c..86ad17e9c52e6 100644 --- a/src/Symfony/Component/Cache/Traits/ArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/ArrayTrait.php @@ -44,7 +44,7 @@ public function hasItem($key) { CacheItem::validateKey($key); - return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); + return isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->deleteItem($key)); } /** @@ -81,7 +81,7 @@ private function generateItems(array $keys, $now, $f) { foreach ($keys as $i => $key) { try { - if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key))) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { $this->values[$key] = $value = null; } elseif (!$this->storeSerialized) { $value = $this->values[$key];