symfony/symfony · error · Symfony\Component\Cache\Exception\InvalidArgumentException

Argument "$beta" provided to "%s::get()" must be a positive

Error message

Argument "$beta" provided to "%s::get()" must be a positive number, %f given.

What it means

doGet() implements stochastic early expiration: $beta weights how early a cached item is recomputed to avoid stampedes. beta defaults to 1.0 and any value below 0 is meaningless (it would never expire), so Symfony rejects it. The message interpolates the offending float and the concrete class via static::class.

Source

Thrown at src/Symfony/Component/Cache/Traits/ContractsTrait.php:65

            if (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
                $this->setCallbackWrapper(null);
            }
        }

        if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) {
            $callbackWrapper = $callbackWrapper(...);
        }

        $previousWrapper = $this->callbackWrapper;
        $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger, ?float $beta = null) => $callback($item, $save);

        return $previousWrapper;
    }

    private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null): mixed
    {
        if (0 > $beta ??= 1.0) {
            throw new InvalidArgumentException(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta));
        }

        static $setMetadata;

        $setMetadata ??= \Closure::bind(
            static function (CacheItem $item, float $startTime, ?array &$metadata) {
                if ($item->expiry > $endTime = microtime(true)) {
                    $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
                    $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
                } else {
                    unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME], $metadata[CacheItem::METADATA_TAGS]);
                }
            },
            null,
            CacheItem::class
        );

        $this->callbackWrapper ??= LockRegistry::compute(...);

View on GitHub (pinned to 698e28026c)

Solutions

  1. Pass beta >= 0; use 0.0 to disable early expiration, or omit it to accept the default 1.0.
  2. Clamp computed beta: max(0.0, $computedBeta).
  3. If you want indefinite lifetime, set it on the item via expiresAfter/expiresAt inside the callback, not via beta.

Example fix

// before
$cache->get('k', $cb, $grace - $now); // can be negative

// after
$cache->get('k', $cb, max(0.0, $grace - $now));
Defensive patterns

Strategy: validation

Validate before calling

$beta = max(0.0, (float) $beta);
$cache->get('key', $cb, $beta);

Type guard

function validBeta(float $beta): bool { return $beta >= 0.0; }

Try / catch

try { $cache->get('k', $cb, $beta); }
catch (\Symfony\Component\Cache\Exception\InvalidArgumentException $e) {
    // retry with default beta
    return $cache->get('k', $cb);
}

Prevention

When it happens

Trigger: Calling $cache->get('key', $cb, -1) explicitly; passing beta computed from arithmetic that can go negative, e.g. $cache->get('k', $cb, $ttl - $now).

Common situations: Misreading beta as 'seconds of early expiration' and passing a negative delta; copying tutorial code that uses -1 to mean 'disable'.

Related errors


AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06). Data as JSON: /api/errors/b18dadbcd265d640. Report an issue: GitHub.