-
Notifications
You must be signed in to change notification settings - Fork 0
Symfony Integration
Rumen Damyanov edited this page Jul 31, 2025
·
1 revision
Complete guide to integrating php-geolocation with Symfony applications using services, event listeners, and best practices.
- Installation & Setup
- Service Configuration
- Event Listeners
- Controller Integration
- Twig Extensions
- Console Commands
- Bundle Development
- Testing Integration
composer require rumenx/geolocationCreate config/packages/geolocation.yaml:
# config/packages/geolocation.yaml
parameters:
geolocation.country_to_language:
US: ['en']
CA: ['en', 'fr']
GB: ['en']
AU: ['en']
DE: ['de']
AT: ['de']
CH: ['de', 'fr', 'it']
FR: ['fr']
BE: ['fr', 'nl']
ES: ['es']
MX: ['es']
AR: ['es']
BR: ['pt']
PT: ['pt']
JP: ['ja']
CN: ['zh']
RU: ['ru']
IT: ['it']
NL: ['nl']
SE: ['sv']
NO: ['no']
DK: ['da']
FI: ['fi']
geolocation.available_languages: ['en', 'fr', 'de', 'es']
geolocation.default_language: 'en'
geolocation.cookie_name: 'app_language'
geolocation.cookie_lifetime: 2592000 # 30 days
geolocation.enable_simulation: '%kernel.debug%'
services:
Rumenx\Geolocation\Geolocation:
arguments:
$server: '@request_stack'
$countryToLanguage: '%geolocation.country_to_language%'
$languageCookieName: '%geolocation.cookie_name%'
public: true
App\Service\GeolocationService:
arguments:
$geolocation: '@Rumenx\Geolocation\Geolocation'
$availableLanguages: '%geolocation.available_languages%'
$defaultLanguage: '%geolocation.default_language%'<?php
// src/Service/GeolocationService.php
namespace App\Service;
use Rumenx\Geolocation\Geolocation;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class GeolocationService
{
private Geolocation $geolocation;
private array $availableLanguages;
private string $defaultLanguage;
private RequestStack $requestStack;
public function __construct(
Geolocation $geolocation,
array $availableLanguages,
string $defaultLanguage,
RequestStack $requestStack
) {
$this->geolocation = $geolocation;
$this->availableLanguages = $availableLanguages;
$this->defaultLanguage = $defaultLanguage;
$this->requestStack = $requestStack;
}
public function getCountryCode(): ?string
{
return $this->geolocation->getCountryCode();
}
public function getLanguageForCountry(?string $country = null): ?string
{
$country = $country ?? $this->getCountryCode();
return $this->geolocation->getLanguageForCountry($country, $this->availableLanguages);
}
public function detectUserLanguage(): string
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) {
return $this->defaultLanguage;
}
// 1. Check URL parameter
if ($request->query->has('lang')) {
$lang = $request->query->get('lang');
if (in_array($lang, $this->availableLanguages)) {
return $lang;
}
}
// 2. Check cookie
if ($request->cookies->has('app_language')) {
$lang = $request->cookies->get('app_language');
if (in_array($lang, $this->availableLanguages)) {
return $lang;
}
}
// 3. Use geolocation
$detectedLang = $this->getLanguageForCountry();
if ($detectedLang && in_array($detectedLang, $this->availableLanguages)) {
return $detectedLang;
}
return $this->defaultLanguage;
}
public function getGeoInfo(array $fields = []): array
{
return $this->geolocation->getGeoInfo($fields);
}
public function isLocalDevelopment(): bool
{
return $this->geolocation->isLocalDevelopment();
}
public function shouldSetLanguageCookie(): bool
{
return $this->geolocation->shouldSetLanguage();
}
public function simulateCountry(string $country): self
{
$simulatedGeo = Geolocation::simulate($country);
return new self(
$simulatedGeo,
$this->availableLanguages,
$this->defaultLanguage,
$this->requestStack
);
}
}<?php
// src/Service/LanguageDetectionService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class LanguageDetectionService
{
private GeolocationService $geoService;
private array $availableLanguages;
private string $cookieName;
private int $cookieLifetime;
public function __construct(
GeolocationService $geoService,
array $availableLanguages,
string $cookieName,
int $cookieLifetime
) {
$this->geoService = $geoService;
$this->availableLanguages = $availableLanguages;
$this->cookieName = $cookieName;
$this->cookieLifetime = $cookieLifetime;
}
public function detectLanguage(Request $request): string
{
// Priority order: URL param → Cookie → Geolocation → Default
return $this->detectFromUrl($request)
?? $this->detectFromCookie($request)
?? $this->detectFromGeolocation()
?? $this->availableLanguages[0];
}
public function setLanguageCookie(Response $response, string $language): void
{
if (in_array($language, $this->availableLanguages)) {
$cookie = Cookie::create(
$this->cookieName,
$language,
time() + $this->cookieLifetime,
'/',
null,
false,
true // httpOnly
);
$response->headers->setCookie($cookie);
}
}
public function createLanguageResponse(string $language): Response
{
$response = new Response();
$this->setLanguageCookie($response, $language);
return $response;
}
private function detectFromUrl(Request $request): ?string
{
$lang = $request->query->get('lang');
return (is_string($lang) && in_array($lang, $this->availableLanguages)) ? $lang : null;
}
private function detectFromCookie(Request $request): ?string
{
$lang = $request->cookies->get($this->cookieName);
return (is_string($lang) && in_array($lang, $this->availableLanguages)) ? $lang : null;
}
private function detectFromGeolocation(): ?string
{
$lang = $this->geoService->detectUserLanguage();
return in_array($lang, $this->availableLanguages) ? $lang : null;
}
}<?php
// src/EventListener/GeolocationListener.php
namespace App\EventListener;
use App\Service\GeolocationService;
use App\Service\LanguageDetectionService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class GeolocationListener implements EventSubscriberInterface
{
private GeolocationService $geoService;
private LanguageDetectionService $languageService;
public function __construct(
GeolocationService $geoService,
LanguageDetectionService $languageService
) {
$this->geoService = $geoService;
$this->languageService = $languageService;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['onKernelRequest', 20], // High priority to run early
],
];
}
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
// Only process master requests
if (!$event->isMainRequest()) {
return;
}
// Skip for API routes or admin routes if needed
if ($this->shouldSkipGeolocation($request)) {
return;
}
// Detect and set language
$language = $this->languageService->detectLanguage($request);
$request->setLocale($language);
// Add geolocation data to request attributes
$geoInfo = $this->geoService->getGeoInfo();
$request->attributes->set('geo_country', $geoInfo['country_code'] ?? null);
$request->attributes->set('geo_language', $language);
$request->attributes->set('geo_info', $geoInfo);
$request->attributes->set('is_development', $this->geoService->isLocalDevelopment());
}
private function shouldSkipGeolocation(Request $request): bool
{
$path = $request->getPathInfo();
// Skip for API routes
if (str_starts_with($path, '/api/')) {
return true;
}
// Skip for admin routes
if (str_starts_with($path, '/admin/')) {
return true;
}
// Skip for asset files
if (preg_match('/\.(css|js|png|jpg|gif|ico|svg|woff|woff2|ttf)$/', $path)) {
return true;
}
return false;
}
}<?php
// src/EventListener/LanguageCookieListener.php
namespace App\EventListener;
use App\Service\GeolocationService;
use App\Service\LanguageDetectionService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LanguageCookieListener implements EventSubscriberInterface
{
private GeolocationService $geoService;
private LanguageDetectionService $languageService;
public function __construct(
GeolocationService $geoService,
LanguageDetectionService $languageService
) {
$this->geoService = $geoService;
$this->languageService = $languageService;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['onKernelResponse', -10], // Low priority
];
}
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
$response = $event->getResponse();
if (!$event->isMainRequest()) {
return;
}
// Set language cookie if it should be set
if ($this->geoService->shouldSetLanguageCookie()) {
$detectedLanguage = $this->geoService->detectUserLanguage();
$this->languageService->setLanguageCookie($response, $detectedLanguage);
}
// Add geolocation headers for debugging (development only)
if ($this->geoService->isLocalDevelopment()) {
$geoInfo = $this->geoService->getGeoInfo();
$response->headers->set('X-Geo-Country', $geoInfo['country_code'] ?? 'unknown');
$response->headers->set('X-Geo-Language', $request->getLocale());
$response->headers->set('X-Geo-Development', 'true');
}
}
}# config/services.yaml
services:
App\Service\GeolocationService:
arguments:
$availableLanguages: '%geolocation.available_languages%'
$defaultLanguage: '%geolocation.default_language%'
App\Service\LanguageDetectionService:
arguments:
$availableLanguages: '%geolocation.available_languages%'
$cookieName: '%geolocation.cookie_name%'
$cookieLifetime: '%geolocation.cookie_lifetime%'
App\EventListener\GeolocationListener:
tags:
- { name: kernel.event_subscriber }
App\EventListener\LanguageCookieListener:
tags:
- { name: kernel.event_subscriber }<?php
// src/Controller/BaseController.php
namespace App\Controller;
use App\Service\GeolocationService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
abstract class BaseController extends AbstractController
{
protected GeolocationService $geoService;
public function __construct(GeolocationService $geoService)
{
$this->geoService = $geoService;
}
protected function getGeoData(): array
{
return [
'country' => $this->geoService->getCountryCode(),
'language' => $this->geoService->detectUserLanguage(),
'is_development' => $this->geoService->isLocalDevelopment(),
];
}
protected function renderWithGeo(string $view, array $parameters = []): Response
{
return $this->render($view, array_merge($parameters, [
'geo' => $this->getGeoData(),
]));
}
}<?php
// src/Controller/HomeController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends BaseController
{
#[Route('/', name: 'app_home')]
public function index(Request $request): Response
{
$geoInfo = $this->geoService->getGeoInfo();
return $this->renderWithGeo('home/index.html.twig', [
'geo_info' => $geoInfo,
'user_country' => $request->attributes->get('geo_country'),
'detected_language' => $request->attributes->get('geo_language'),
]);
}
#[Route('/country/{country}', name: 'app_simulate_country')]
public function simulateCountry(string $country): Response
{
// For development/testing only
if (!$this->geoService->isLocalDevelopment()) {
throw $this->createNotFoundException('Simulation only available in development');
}
$simulatedGeo = $this->geoService->simulateCountry($country);
$geoInfo = $simulatedGeo->getGeoInfo();
return $this->render('home/simulation.html.twig', [
'simulated_country' => $country,
'geo_info' => $geoInfo,
'geo' => [
'country' => $country,
'language' => $simulatedGeo->detectUserLanguage(),
'is_development' => true,
],
]);
}
}<?php
// src/Controller/Api/GeolocationController.php
namespace App\Controller\Api;
use App\Service\GeolocationService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/api/geolocation', name: 'api_geolocation_')]
class GeolocationController extends AbstractController
{
private GeolocationService $geoService;
public function __construct(GeolocationService $geoService)
{
$this->geoService = $geoService;
}
#[Route('/detect', name: 'detect', methods: ['GET'])]
public function detect(Request $request): JsonResponse
{
try {
$geoInfo = $this->geoService->getGeoInfo();
return $this->json([
'success' => true,
'data' => [
'country_code' => $geoInfo['country_code'] ?? null,
'language' => $this->geoService->detectUserLanguage(),
'ip' => $geoInfo['ip'] ?? null,
'is_development' => $this->geoService->isLocalDevelopment(),
'geo_info' => $geoInfo,
],
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'error' => 'Geolocation detection failed',
'message' => $e->getMessage(),
], 500);
}
}
#[Route('/countries', name: 'countries', methods: ['GET'])]
public function getSupportedCountries(): JsonResponse
{
// Return list of supported countries for simulation
$countries = [
'US' => 'United States',
'CA' => 'Canada',
'GB' => 'United Kingdom',
'DE' => 'Germany',
'FR' => 'France',
'ES' => 'Spain',
'JP' => 'Japan',
'BR' => 'Brazil',
];
return $this->json([
'success' => true,
'data' => $countries,
]);
}
#[Route('/simulate/{country}', name: 'simulate', methods: ['POST'])]
public function simulate(string $country): JsonResponse
{
if (!$this->geoService->isLocalDevelopment()) {
return $this->json([
'success' => false,
'error' => 'Simulation only available in development mode',
], 403);
}
try {
$simulatedGeo = $this->geoService->simulateCountry($country);
$geoInfo = $simulatedGeo->getGeoInfo();
return $this->json([
'success' => true,
'data' => [
'simulated_country' => $country,
'geo_info' => $geoInfo,
'detected_language' => $simulatedGeo->detectUserLanguage(),
],
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'error' => 'Simulation failed',
'message' => $e->getMessage(),
], 500);
}
}
}<?php
// src/Controller/LanguageController.php
namespace App\Controller;
use App\Service\LanguageDetectionService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/language', name: 'app_language_')]
class LanguageController extends AbstractController
{
private LanguageDetectionService $languageService;
public function __construct(LanguageDetectionService $languageService)
{
$this->languageService = $languageService;
}
#[Route('/set/{language}', name: 'set')]
public function setLanguage(string $language, Request $request): RedirectResponse
{
$response = $this->languageService->createLanguageResponse($language);
// Redirect back to previous page
$referer = $request->headers->get('referer');
$redirectUrl = $referer ?: $this->generateUrl('app_home');
return $this->redirect($redirectUrl, 302, $response->headers->all());
}
#[Route('/auto-detect', name: 'auto_detect')]
public function autoDetect(Request $request): RedirectResponse
{
$detectedLanguage = $this->languageService->detectLanguage($request);
return $this->redirectToRoute('app_language_set', [
'language' => $detectedLanguage,
]);
}
}<?php
// src/Twig/GeolocationExtension.php
namespace App\Twig;
use App\Service\GeolocationService;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class GeolocationExtension extends AbstractExtension
{
private GeolocationService $geoService;
public function __construct(GeolocationService $geoService)
{
$this->geoService = $geoService;
}
public function getFunctions(): array
{
return [
new TwigFunction('geo_country', [$this, 'getCountry']),
new TwigFunction('geo_language', [$this, 'getLanguage']),
new TwigFunction('geo_info', [$this, 'getGeoInfo']),
new TwigFunction('geo_is_development', [$this, 'isDevelopment']),
new TwigFunction('geo_simulate', [$this, 'simulateCountry']),
];
}
public function getCountry(): ?string
{
return $this->geoService->getCountryCode();
}
public function getLanguage(): string
{
return $this->geoService->detectUserLanguage();
}
public function getGeoInfo(array $fields = []): array
{
return $this->geoService->getGeoInfo($fields);
}
public function isDevelopment(): bool
{
return $this->geoService->isLocalDevelopment();
}
public function simulateCountry(string $country): array
{
if (!$this->geoService->isLocalDevelopment()) {
return [];
}
$simulatedGeo = $this->geoService->simulateCountry($country);
return $simulatedGeo->getGeoInfo();
}
}{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome{% endblock %}</title>
{% if geo_is_development() %}
<meta name="geo-development" content="true">
<meta name="geo-country" content="{{ geo_country() }}">
<meta name="geo-language" content="{{ geo_language() }}">
{% endif %}
</head>
<body>
{% if geo_is_development() %}
<div class="dev-banner">
<strong>Development Mode:</strong>
Country: {{ geo_country() ?? 'Unknown' }} |
Language: {{ geo_language() }} |
<a href="{{ path('app_language_auto_detect') }}">Auto-detect language</a>
</div>
{% endif %}
<nav>
<div class="language-switcher">
<select onchange="window.location.href='/language/set/' + this.value">
<option value="en" {{ app.request.locale == 'en' ? 'selected' : '' }}>English</option>
<option value="fr" {{ app.request.locale == 'fr' ? 'selected' : '' }}>Français</option>
<option value="de" {{ app.request.locale == 'de' ? 'selected' : '' }}>Deutsch</option>
<option value="es" {{ app.request.locale == 'es' ? 'selected' : '' }}>Español</option>
</select>
</div>
</nav>
<main>
{% block body %}{% endblock %}
</main>
{% block javascripts %}
{% if geo_is_development() %}
<script>
console.log('Geolocation Info:', {{ geo_info()|json_encode|raw }});
</script>
{% endif %}
{% endblock %}
</body>
</html>{# templates/home/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Welcome from {{ geo_country() ?? 'Unknown' }}{% endblock %}
{% block body %}
<h1>
{% if geo_country() == 'US' %}
🇺🇸 Welcome to our US visitors!
{% elseif geo_country() == 'DE' %}
🇩🇪 Willkommen in Deutschland!
{% elseif geo_country() == 'FR' %}
🇫🇷 Bienvenue en France!
{% elseif geo_country() == 'ES' %}
🇪🇸 ¡Bienvenido a España!
{% else %}
🌍 Welcome to our international visitors!
{% endif %}
</h1>
<div class="geo-info">
<h2>Your Information</h2>
<ul>
<li><strong>Country:</strong> {{ geo_country() ?? 'Unknown' }}</li>
<li><strong>Language:</strong> {{ geo_language() }}</li>
<li><strong>Development Mode:</strong> {{ geo_is_development() ? 'Yes' : 'No' }}</li>
</ul>
</div>
{% if geo_is_development() %}
<div class="simulation-tools">
<h3>Test Different Countries</h3>
<div class="country-buttons">
<a href="{{ path('app_simulate_country', {country: 'US'}) }}" class="btn">🇺🇸 USA</a>
<a href="{{ path('app_simulate_country', {country: 'DE'}) }}" class="btn">🇩🇪 Germany</a>
<a href="{{ path('app_simulate_country', {country: 'FR'}) }}" class="btn">🇫🇷 France</a>
<a href="{{ path('app_simulate_country', {country: 'JP'}) }}" class="btn">🇯🇵 Japan</a>
</div>
</div>
{% endif %}
{% endblock %}<?php
// src/Command/GeolocationTestCommand.php
namespace App\Command;
use App\Service\GeolocationService;
use Rumenx\Geolocation\Geolocation;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:geo:test',
description: 'Test geolocation functionality',
)]
class GeolocationTestCommand extends Command
{
private GeolocationService $geoService;
public function __construct(GeolocationService $geoService)
{
$this->geoService = $geoService;
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('country', InputArgument::OPTIONAL, 'Country code to simulate')
->setHelp('Test geolocation detection or simulate a specific country');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$country = $input->getArgument('country');
if ($country) {
return $this->testSimulation($io, $country);
} else {
return $this->testCurrent($io);
}
}
private function testCurrent(SymfonyStyle $io): int
{
$io->title('Current Geolocation Test');
$geoInfo = $this->geoService->getGeoInfo();
$io->table(
['Property', 'Value'],
[
['Country', $geoInfo['country_code'] ?? 'Not detected'],
['Language', $this->geoService->detectUserLanguage()],
['IP', $geoInfo['ip'] ?? 'Not available'],
['Development Mode', $this->geoService->isLocalDevelopment() ? 'Yes' : 'No'],
['Browser', json_encode($geoInfo['browser'] ?? [])],
['OS', $geoInfo['os'] ?? 'Not detected'],
['Device', $geoInfo['device'] ?? 'Not detected'],
]
);
return Command::SUCCESS;
}
private function testSimulation(SymfonyStyle $io, string $country): int
{
$io->title("Simulation Test for {$country}");
try {
$simulatedGeo = $this->geoService->simulateCountry($country);
$geoInfo = $simulatedGeo->getGeoInfo();
$io->table(
['Property', 'Value'],
collect($geoInfo)->map(function ($value, $key) {
return [$key, is_array($value) ? json_encode($value) : (string) $value];
})->toArray()
);
$io->success("Simulation for {$country} completed successfully");
return Command::SUCCESS;
} catch (\Exception $e) {
$io->error("Simulation failed: " . $e->getMessage());
return Command::FAILURE;
}
}
}<?php
// src/Command/LanguageDetectionCommand.php
namespace App\Command;
use App\Service\LanguageDetectionService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:language:test',
description: 'Test language detection logic',
)]
class LanguageDetectionCommand extends Command
{
private LanguageDetectionService $languageService;
public function __construct(LanguageDetectionService $languageService)
{
$this->languageService = $languageService;
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Language Detection Test');
$testCases = [
['country' => 'US', 'accept_lang' => 'en-US,en;q=0.9', 'expected' => 'en'],
['country' => 'DE', 'accept_lang' => 'de-DE,de;q=0.9,en;q=0.8', 'expected' => 'de'],
['country' => 'FR', 'accept_lang' => 'fr-FR,fr;q=0.9,en;q=0.8', 'expected' => 'fr'],
['country' => 'CA', 'accept_lang' => 'fr-CA,fr;q=0.9,en;q=0.8', 'expected' => 'fr'],
['country' => 'CH', 'accept_lang' => 'de-CH,de;q=0.9,fr;q=0.8', 'expected' => 'de'],
];
$results = [];
foreach ($testCases as $case) {
// Simulate environment for each test case
$_SERVER['HTTP_CF_IPCOUNTRY'] = $case['country'];
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $case['accept_lang'];
// Create mock request for testing
$mockRequest = new \Symfony\Component\HttpFoundation\Request();
$mockRequest->headers->set('Accept-Language', $case['accept_lang']);
// Note: This is simplified - in real testing you'd mock the request properly
$detected = $case['expected']; // Placeholder for actual detection
$status = $detected === $case['expected'] ? '✅' : '❌';
$results[] = [
$case['country'],
$case['accept_lang'],
$case['expected'],
$detected,
$status
];
}
$io->table(
['Country', 'Accept-Language', 'Expected', 'Detected', 'Status'],
$results
);
return Command::SUCCESS;
}
}<?php
// src/GeolocationBundle/GeolocationBundle.php
namespace App\GeolocationBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class GeolocationBundle extends Bundle
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
}
}<?php
// src/GeolocationBundle/DependencyInjection/Configuration.php
namespace App\GeolocationBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('geolocation');
$treeBuilder->getRootNode()
->children()
->arrayNode('country_to_language')
->useAttributeAsKey('country')
->arrayPrototype()
->scalarPrototype()->end()
->end()
->end()
->arrayNode('available_languages')
->scalarPrototype()->end()
->end()
->scalarNode('default_language')->defaultValue('en')->end()
->scalarNode('cookie_name')->defaultValue('app_language')->end()
->integerNode('cookie_lifetime')->defaultValue(2592000)->end()
->booleanNode('enable_simulation')->defaultValue('%kernel.debug%')->end()
->end();
return $treeBuilder;
}
}<?php
// tests/Controller/GeolocationControllerTest.php
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class GeolocationControllerTest extends WebTestCase
{
public function testHomepageWithGeolocation(): void
{
$client = static::createClient();
// Simulate German visitor
$client->getContainer()->get('test.private.request_stack')
->getCurrentRequest()
->server->set('HTTP_CF_IPCOUNTRY', 'DE');
$crawler = $client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Willkommen');
}
public function testApiGeolocationEndpoint(): void
{
$client = static::createClient();
$client->request('GET', '/api/geolocation/detect');
$this->assertResponseIsSuccessful();
$this->assertJson($client->getResponse()->getContent());
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertTrue($data['success']);
$this->assertArrayHasKey('data', $data);
}
public function testLanguageSwitch(): void
{
$client = static::createClient();
$client->request('GET', '/language/set/de');
$this->assertResponseRedirects();
// Check if cookie is set
$cookies = $client->getResponse()->headers->getCookies();
$languageCookie = null;
foreach ($cookies as $cookie) {
if ($cookie->getName() === 'app_language') {
$languageCookie = $cookie;
break;
}
}
$this->assertNotNull($languageCookie);
$this->assertEquals('de', $languageCookie->getValue());
}
}- 📊 Client Detection - Browser, device, and resolution detection
- 📚 API Reference - Complete class and method documentation
- ⚙️ Configuration - Advanced configuration options
- 🔧 Troubleshooting - Common issues and solutions
Previous: Language Negotiation | Next: API Reference