netbull/mobile-detect-bundle
Symfony bundle integrating Mobile Detect to identify phones, tablets, and desktop browsers. Provides services and Twig helpers for device-aware templates, redirects, and responsive behavior in Laravel-style PHP apps.
First, a reality check on scope. netbull/mobile-detect-bundle is a Symfony bundle, not a Laravel package. It cannot be composer required into a Laravel app (it depends on symfony/framework-bundle, DI, Twig, and a kernel request listener). What you actually get day-to-day splits into two paths:
mobiledetect/mobiledetectlib and borrow the bundle's three ideas: a detector service, a view-switch parameter, and a redirect listener.composer require netbull/mobile-detect-bundle
Check the resolved version before you plan anything. The project is on a 7.x line and shipped v7.0.2 in May 2025, so it is actively maintained and the major version is roughly aligned with the modern Symfony generation. Don't trust a "Symfony 2/3/4/5 only" line you may have read in older docs — inspect the constraints directly:
composer show netbull/mobile-detect-bundle --all | grep -E 'versions|requires'
Register the bundle (namespace is MobileDetectBundle\):
// config/bundles.php
return [
// ...
MobileDetectBundle\MobileDetectBundle::class => ['all' => true],
];
Then look at the config tree first — the whole package is config-driven, so config/packages/mobile_detect.yaml is your real documentation:
mobile_detect:
devices:
mobile: ['/iphone/i', '/android.*mobile/i']
tablet: ['/ipad/i']
switch_device_view:
device_view_enabled: true # enables the ?device_view= / cookie override
default_view: desktop
devices:
mobile: mobile
tablet: tablet
redirect:
mobile:
host: m.example.com
action: redirect
status_code: 302 # 302 while rolling out, 301 only when settled
tablet:
host: tablet.example.com
detect_tablet_as_mobile: false
Verify what actually loaded rather than trusting the README:
bin/console debug:config mobile_detect
bin/console debug:twig | grep -i -E 'mobile|tablet|device'
First use case to implement: the "View desktop site" escape hatch. Wire the switch parameter into your layout, because it's the single feature that saves you from a support ticket storm when UA detection guesses wrong:
{% if is_mobile() and not device_view_is_full() %}
<a href="?device_view=desktop">View desktop site</a>
{% endif %}
composer require mobiledetect/mobiledetectlib
// app/Http/Middleware/RedirectMobile.php
public function handle($request, Closure $next)
{
$detect = new \Detection\MobileDetect(); // v3 namespace; v2 is Mobile_Detect
$excluded = $request->is('api/*', 'webhooks/*', 'assets/*', 'livewire/*');
if (! $excluded
&& $detect->isMobile()
&& ! $detect->isTablet()
&& $request->getHost() !== 'm.example.com'
&& $request->cookie('device_view') !== 'desktop'
) {
return redirect()->away('https://m.example.com'.$request->getRequestUri(), 302);
}
return $next($request);
}
1. Inject the detector, not a facade of the detector. The bundle exposes MobileDetectorInterface and a DeviceView helper. Autowire them into controllers/services rather than calling globals — this is what makes your code testable:
use MobileDetectBundle\DeviceDetector\MobileDetectorInterface;
use MobileDetectBundle\Helper\DeviceView;
final class HomeController extends AbstractController
{
public function __construct(
private MobileDetectorInterface $detector,
private DeviceView $deviceView,
) {}
public function __invoke(): Response
{
$view = $this->deviceView->getViewType(); // desktop|tablet|mobile
$template = match ($view) {
'mobile' => 'home/mobile.html.twig',
'tablet' => 'home/tablet.html.twig',
default => 'home/desktop.html.twig',
};
return $this->render($template, ['detector' => $this->detector]);
}
}
2. Template-per-device over one responsive template — only when you must. The bundle's switch_device_view is designed for the legacy pattern of separate mobile/tablet/desktop templates. Use it for email, PDF, or a genuinely different mobile UX; don't use it to reinvent CSS media queries.
3. Keep detection behind your own interface. Wrap the bundle (or the raw library in Laravel) in App\Device\DeviceResolver with isMobile(), isTablet(), preferredView(). When UA sniffing inevitably gets replaced by Client Hints (Sec-CH-UA-Mobile) or a responsive-only approach, you swap one class instead of grepping the codebase.
4. Redirect listener hygiene. Redirects run on every request. In practice you want a subscriber that short-circuits before the bundle's listener for paths that must never redirect: /api, /webhooks, /health, static assets, and any route with an _no_mobile_redirect default. In Laravel, that's the $excluded check above; in Symfony it's a higher-priority kernel.request listener.
5. Test with an explicit UA table, not live detection.
$client = static::createClient();
$client->setServerParameter('HTTP_USER_AGENT',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15');
$client->request('GET', '/');
self::assertResponseRedirects('https://m.example.com/');
Also unit-test DeviceResolver with a fixed array of [ua => expectedView] so a library upgrade can't silently change routing.
6. Twig-level branching for partials is cheap and fine: {% if is_mobile() %} around a heavy carousel or an autoplay video. Keep it to component-level decisions; never use it for layout.
7. Pin the patch, then upgrade deliberately. v7.0.2 is a bug-fix-only release (a deprecation fix), so it's a safe in-range bump — but pin an exact version in composer.json and read every changelog entry for the 7.x line before bumping the minor, because this project's release notes are terse (* deprecation) and tell you nothing about behavioural impact.
composer.json — take mobiledetect/mobiledetectlib and the three design ideas (detector service, view-switch parameter, redirect listener).7.x line with a 2025 patch release points at modern Symfony, contradicting older "Symfony 2/3/4/5 only" notes. Run composer why-not netbull/mobile-detect-bundle and composer show --all before designing around it; if composer blocks you, fall back to the Laravel/library route rather than forking blindly.7.0.1, you were probably logging deprecation notices from bundle code on every request. Bumping to 7.0.2 clears them — worth doing, zero-risk, but it is not a feature release and changes nothing about UA behaviour.App\Device\DeviceResolver) thin enough that you could vendor the handful of classes you actually use (detector wrapper + listener + DeviceView) into your own namespace.isTablet() returns false on modern iPads. ChromeOS tablets and Android tablets are similarly messy. Treat isTablet() as a heuristic and default to the responsive view.status_code: 302. Users who get a bad 301 to m.example.com cannot be rescued by a config change.device_view cookie or the UA, your CDN/Varnish must Vary: User-Agent (and ideally Vary: Sec-CH-UA-Mobile, Sec-CH-UA-Platform). Otherwise the first mobile visitor's redirect gets served to desktop users. This is the most common production incident with bundles like this.detect_tablet_as_mobile lives under redirect, not at the root; devices appears both at root (regexes) and under switch_device_view (template names). Always confirm with bin/console debug:config mobile_detect after editing.is_mobile(), is_tablet(), and the device_view_* helpers vary between releases; debug:twig is faster than reading source. If a template throws "unknown function," you're on a version whose extension didn't register.MobileDetectorInterface isn't aliased, inject the concrete service or define an alias in services.yaml. Locate the real id with bin/console debug:container | grep -i mobile.device_view_enabled: false, remove the redirect block) and retest. Symptom "only curl/API requests get redirected" is almost always the global listener, not your code.devices; decorating DeviceView to add a "remember my choice" cookie; and a kernel.request subscriber with higher priority than the bundle's listener to skip redirects for specific routes. In Laravel the equivalents are a route-group-scoped middleware and a View::composer or Blade directive.Sec-CH-UA-Mobile server hints. Use this bundle (or the library) only for the redirect/escape-hatch layer and for emails, where UA sniffing is still the only signal you get.How can I help you explore Laravel packages today?