1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Cache\Traits; 13 14use Psr\Log\LoggerInterface; 15use Symfony\Component\Cache\Adapter\AdapterInterface; 16use Symfony\Component\Cache\CacheItem; 17use Symfony\Component\Cache\Exception\InvalidArgumentException; 18use Symfony\Component\Cache\LockRegistry; 19use Symfony\Contracts\Cache\CacheInterface; 20use Symfony\Contracts\Cache\CacheTrait; 21use Symfony\Contracts\Cache\ItemInterface; 22 23/** 24 * @author Nicolas Grekas <p@tchwork.com> 25 * 26 * @internal 27 */ 28trait ContractsTrait 29{ 30 use CacheTrait { 31 doGet as private contractsGet; 32 } 33 34 private $callbackWrapper = [LockRegistry::class, 'compute']; 35 private $computing = []; 36 37 /** 38 * Wraps the callback passed to ->get() in a callable. 39 * 40 * @return callable the previous callback wrapper 41 */ 42 public function setCallbackWrapper(?callable $callbackWrapper): callable 43 { 44 $previousWrapper = $this->callbackWrapper; 45 $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { 46 return $callback($item, $save); 47 }; 48 49 return $previousWrapper; 50 } 51 52 private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) 53 { 54 if (0 > $beta = $beta ?? 1.0) { 55 throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); 56 } 57 58 static $setMetadata; 59 60 $setMetadata = $setMetadata ?? \Closure::bind( 61 static function (CacheItem $item, float $startTime, ?array &$metadata) { 62 if ($item->expiry > $endTime = microtime(true)) { 63 $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; 64 $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); 65 } else { 66 unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]); 67 } 68 }, 69 null, 70 CacheItem::class 71 ); 72 73 return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { 74 // don't wrap nor save recursive calls 75 if (isset($this->computing[$key])) { 76 $value = $callback($item, $save); 77 $save = false; 78 79 return $value; 80 } 81 82 $this->computing[$key] = $key; 83 $startTime = microtime(true); 84 85 try { 86 $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { 87 $setMetadata($item, $startTime, $metadata); 88 }, $this->logger ?? null); 89 $setMetadata($item, $startTime, $metadata); 90 91 return $value; 92 } finally { 93 unset($this->computing[$key]); 94 } 95 }, $beta, $metadata, $this->logger ?? null); 96 } 97} 98