diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index af6d793033fc4..96ff653c3fa1a 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -65,15 +65,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; } 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 1f471b663137b..13c1a7b05647f 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -73,7 +73,7 @@ function (CacheItemInterface $innerItem, array $item) { $item["\0*\0value"] = array("\x9D".pack('VN', $stats[CacheItem::STATS_EXPIRY] - CacheItem::STATS_EXPIRY_OFFSET, $stats[CacheItem::STATS_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', (int) $item["\0*\0expiry"]) : null); }, null, CacheItem::class @@ -192,7 +192,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 1f5f539c8a07c..9cd4d00b4c2ca 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.2.0 ----- + * added sub-second expiry accuracy for backends that support it * added `CacheInterface` and `TaggableCacheInterface`, providing stampede protection via probabilistic early expiration * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getStats()` instead diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 8aa17df860ec2..9d8f28fe1769d 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/Fixtures/ArrayCache.php b/src/Symfony/Component/Cache/Tests/Fixtures/ArrayCache.php index 1a6157e822117..12081fa0411f4 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..28a152bb53e02 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)); } /** diff --git a/src/Symfony/Component/Cache/Traits/GetTrait.php b/src/Symfony/Component/Cache/Traits/GetTrait.php index 71823c321e7c0..27f91fd4e08d5 100644 --- a/src/Symfony/Component/Cache/Traits/GetTrait.php +++ b/src/Symfony/Component/Cache/Traits/GetTrait.php @@ -69,7 +69,7 @@ private function doGet(CacheItemPoolInterface $pool, string $key, callable $call $save = \Closure::bind( function (CacheItemPoolInterface $pool, CacheItemInterface $item, $value, float $startTime) { if ($item instanceof CacheItem && $startTime && $item->expiry > $endTime = microtime(true)) { - $item->newStats[CacheItem::STATS_EXPIRY] = $item->expiry; + $item->newStats[CacheItem::STATS_EXPIRY] = (int) $item->expiry; $item->newStats[CacheItem::STATS_CTIME] = 1000 * (int) ($endTime - $startTime); } $pool->save($item->set($value));