serbanghita/Mobile-Detect · error · MobileDetectException

IS_MOBILE_ERR

IS_MOBILE_ERR

Error message

Cache problem in isMobile(): {$e->getMessage()}

What it means

isMobile() wraps every cache interaction (createCacheKey(), cache->get(), cache->set()) in a try/catch and re-throws cache-layer failures — CacheException, CacheInvalidArgumentException, or a PSR Psr\SimpleCache\InvalidArgumentException — as MobileDetectException with code IS_MOBILE_ERR (0x2), chaining the original as previous and embedding its message. With the default setup (in-memory Cache + 'sha1' key fn) this is nearly unreachable; it appears when the cache layer was swapped or misconfigured.

Source

Thrown at src/MobileDetect.php:1445

            // Special case: Amazon CloudFront mobile viewer
            if (
                $this->getUserAgent() === static::$cloudFrontUA &&
                $this->getHttpHeader('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER') === 'true'
            ) {
                $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                return true;
            }

            if ($this->hasHttpHeaders() && $this->checkHttpHeadersForMobile()) {
                $this->cache->set($cacheKey, true, $this->config['cacheTtl']);
                return true;
            } else {
                $result = $this->matchUserAgentWithFirstFoundMatchingRule();
                $this->cache->set($cacheKey, $result, $this->config['cacheTtl']);
                return $result;
            }
        } catch (CacheInvalidArgumentException | CacheException | PsrInvalidArgumentException $e) {
            throw new MobileDetectException("Cache problem in isMobile(): {$e->getMessage()}", MobileDetectExceptionCode::IS_MOBILE_ERR, $e);
        }
    }

    /**
     * Check if the device is a tablet.
     * Return true if any type of tablet device is detected.
     * @return bool
     * @throws MobileDetectException
     */
    public function isTablet(): bool
    {
        if (!$this->hasUserAgent()) {
            throw new MobileDetectException('No user-agent has been set.', MobileDetectExceptionCode::INVALID_USER_AGENT_ERR);
        }

        if ($this->isUserAgentEmpty()) {
            return false;
        }

View on GitHub (pinned to 6ab7b0404d)

Solutions

  1. Inspect the chained exception: $e->getPrevious()->getMessage() names the real cache problem — fix that root cause first.
  2. Keep 'cacheKeyFn' as 'sha1' (or md5 / a hash() closure) so keys always satisfy the bundled Cache's charset and length rule.
  3. Verify any injected CacheInterface is PSR-16 compliant: get($key, $default), set($key, $value, $ttl) with int|DateInterval|null TTL, throwing Psr\SimpleCache\InvalidArgumentException for bad arguments only.
  4. If detection must never hard-fail, catch MobileDetectException, match IS_MOBILE_ERR, and fall back to a default or a cache-less detector instance.

Example fix

// before
$detect = new MobileDetect(['cacheKeyFn' => 'base64']); // not a real function
$detect->isMobile();
// MobileDetectException: Cache problem in isMobile(): cacheKeyFn is not a function.

// after
$detect = new MobileDetect(); // default 'sha1'
try {
    $isMobile = $detect->isMobile();
} catch (MobileDetectException $e) {
    if ($e->getCode() === MobileDetectExceptionCode::IS_MOBILE_ERR) {
        $isMobile = false; // degrade gracefully; log $e->getPrevious()
    } else {
        throw $e;
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Prevent the common root cause before detection:
$config = ['cacheKeyFn' => 'sha1'];
if (isset($customFn) && !is_callable($customFn)) {
    throw new \InvalidArgumentException('cacheKeyFn must be callable, e.g. sha1');
}
$detect = new MobileDetect($config);

Type guard

function detectorCacheConfigIsSafe(array $config): bool
{
    $fn = $config['cacheKeyFn'] ?? 'sha1';
    if (!is_callable($fn)) { return false; }
    $sample = $fn('probe:Mozilla/5.0 (X) HTTP_ACCEPT=text/html');
    return is_string($sample) && preg_match('/^[A-Za-z0-9_.]{1,64}$/', $sample) === 1;
}

Try / catch

catch (MobileDetectException $e) {
    if ($e->getCode() === MobileDetectExceptionCode::IS_MOBILE_ERR) {
        // cache layer failed; $e->getPrevious() holds the CacheException details
        $isMobile = false; // or retry with a fresh in-memory detector
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Configuring 'cacheKeyFn' to a non-callable (you then see the inner 'cacheKeyFn is not a function.' from createCacheKey, src/MobileDetect.php:1733, wrapped in this message); replacing the PSR-16 cache with a custom Redis/Memcached/filesystem adapter whose get() or set() throws a PSR InvalidArgumentException; a custom cacheKeyFn returning keys that violate /^[A-Za-z0-9_.]{1,64}$/, making the bundled Cache reject them on every call.

Common situations: Integrating a PSR-6-to-PSR-16 bridge or Symfony Cache adapter with stricter key rules; DI wiring that injects the wrong service into the cache slot; production-only cache backends failing (connection down, serialization error) while dev used the default in-memory cache and never exercised the path.

Related errors


AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21). Data as JSON: /api/errors/d8b5621fb0434e15. Report an issue: GitHub.