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
- Inspect the chained exception: $e->getPrevious()->getMessage() names the real cache problem — fix that root cause first.
- Keep 'cacheKeyFn' as 'sha1' (or md5 / a hash() closure) so keys always satisfy the bundled Cache's charset and length rule.
- 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.
- 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
- Keep the default sha1 cacheKeyFn unless you must customize; any hash callback works.
- Smoke-test custom PSR-16 backends with the key shape MobileDetect produces (40-char sha1 hex) before deploying.
- Match on exception code, not message text — messages embed inner wording that can change between releases.
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
- IS_TABLET_ERR
- IS_MAGIC_ERR
- Cache key must be a string.
- Invalid key: '$key'. Must be alphanumeric, can contain _ and
- TTL must be null, int, or DateInterval.
AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21).
Data as JSON: /api/errors/d8b5621fb0434e15.
Report an issue: GitHub.