You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony 4.4全局货币下拉菜单设置及多前缀路由疑问

Symfony 4.4 全站货币切换方案(支持_locale路由前缀)

我来给你梳理一个完整可落地的实现方案,完美适配你已有_locale路由前缀的场景,解决默认货币初始化、路由多前缀适配、货币切换这几个核心问题:

1. 事件订阅者处理默认货币与路由重定向

首先创建一个事件订阅者,在请求入口统一处理货币初始化,以及路由的自动补全(如果用户访问不带货币参数的路由,自动重定向到带默认货币的路由)。

// src/EventSubscriber/CurrencySubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class CurrencySubscriber implements EventSubscriberInterface
{
    private $session;
    private $urlGenerator;
    // 定义locale与默认货币的映射关系
    private $localeCurrencyMap = [
        'en' => 'USD',
        'fr' => 'EUR',
        'de' => 'EUR',
        'es' => 'EUR',
        // 可根据你的业务需求扩展
    ];

    public function __construct(\Symfony\Component\HttpFoundation\Session\SessionInterface $session, UrlGeneratorInterface $urlGenerator)
    {
        $this->session = $session;
        $this->urlGenerator = $urlGenerator;
    }

    public function onKernelRequest(RequestEvent $event)
    {
        // 只处理主请求,忽略子请求(比如Twig渲染的嵌入式控制器)
        if (!$event->isMasterRequest()) {
            return;
        }

        $request = $event->getRequest();
        $currentLocale = $request->getLocale();
        $routeCurrency = $request->get('currency');

        // 步骤1:初始化会话中的货币(如果未设置)
        if (!$this->session->has('currency')) {
            $defaultCurrency = $this->localeCurrencyMap[$currentLocale] ?? 'USD';
            $this->session->set('currency', $defaultCurrency);
        }

        // 步骤2:如果当前路由没有货币参数,重定向到带正确货币的路由
        if (empty($routeCurrency)) {
            $targetRoute = $request->get('_route');
            $routeParams = $request->get('_route_params');
            $routeParams['currency'] = $this->session->get('currency');
            $routeParams['_locale'] = $currentLocale;

            $redirectUrl = $this->urlGenerator->generate($targetRoute, $routeParams);
            $event->setResponse(new RedirectResponse($redirectUrl));
        } else {
            // 同步路由中的货币与会话(避免用户手动修改URL参数导致不一致)
            if ($routeCurrency !== $this->session->get('currency')) {
                $this->session->set('currency', $routeCurrency);
            }
        }
    }

    public static function getSubscribedEvents()
    {
        // 优先级设为9,确保在Symfony自带的LocaleListener(优先级16)之后执行,保证locale已被解析
        return [
            KernelEvents::REQUEST => ['onKernelRequest', 9],
        ];
    }
}

2. 配置路由多前缀(_locale + currency)

修改你的路由配置,给所有业务路由加上/{_locale}/{currency}前缀,同时单独处理货币切换的路由(避免被前缀覆盖):

# config/routes.yaml
# 单独定义货币切换路由,不受前缀影响
switch_currency:
    path: /switch-currency
    controller: App\Controller\CurrencyController::switchCurrency

# 业务路由统一加上_locale和currency前缀
app:
    resource: '../src/Controller/'
    type: annotation
    prefix: '/{_locale}/{currency}'
    requirements:
        _locale: en|fr|de|es # 你的支持的locale列表
        currency: USD|EUR|GBP # 你的支持的货币列表
    defaults:
        _locale: '%locale%' # 系统默认locale

3. 实现货币切换控制器

创建一个简单的控制器,处理用户的货币切换请求,更新会话后重定向回原页面:

// src/Controller/CurrencyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class CurrencyController extends AbstractController
{
    /**
     * @Route("/switch-currency", name="switch_currency")
     */
    public function switchCurrency(Request $request): Response
    {
        $newCurrency = $request->request->get('currency');
        $allowedCurrencies = ['USD', 'EUR', 'GBP'];

        // 验证货币是否在允许列表内
        if (in_array($newCurrency, $allowedCurrencies)) {
            $this->get('session')->set('currency', $newCurrency);
        }

        // 重定向回用户之前访问的页面
        $referer = $request->headers->get('referer');
        return $this->redirect($referer ?? $this->generateUrl('homepage'));
    }
}

4. 模板中添加货币下拉菜单

在你的基础模板(base.html.twig)里添加下拉菜单,绑定到货币切换路由,同时处理CSRF保护:

{# templates/base.html.twig #}
<form method="POST" action="{{ path('switch_currency') }}" class="currency-switcher">
    <select name="currency" onchange="this.form.submit()">
        {% set currentCurrency = app.session.get('currency') %}
        <option value="USD" {{ currentCurrency == 'USD' ? 'selected' }}>USD ($)</option>
        <option value="EUR" {{ currentCurrency == 'EUR' ? 'selected' }}>EUR (€)</option>
        <option value="GBP" {{ currentCurrency == 'GBP' ? 'selected' }}>GBP (£)</option>
    </select>
    <!-- Symfony CSRF保护令牌 -->
    <input type="hidden" name="_token" value="{{ csrf_token('switch_currency') }}">
</form>

5. 价格转换服务(可选但推荐)

创建一个服务封装价格转换逻辑,方便在控制器和模板中复用:

// src/Service/CurrencyConverter.php
namespace App\Service;

use Symfony\Component\HttpFoundation\Session\SessionInterface;

class CurrencyConverter
{
    private $session;
    // 系统基准货币(建议用内部统一存储的货币,比如EUR)
    private $baseCurrency = 'EUR';
    // 示例汇率,实际项目建议用API(如ECB、Fixer.io)实时获取或缓存
    private $exchangeRates = [
        'EUR' => 1.0,
        'USD' => 1.09,
        'GBP' => 0.86,
    ];

    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    public function convert(float $basePrice): float
    {
        $targetCurrency = $this->session->get('currency', $this->baseCurrency);
        return isset($this->exchangeRates[$targetCurrency]) 
            ? $basePrice * $this->exchangeRates[$targetCurrency] 
            : $basePrice;
    }

    public function getCurrentCurrency(): string
    {
        return $this->session->get('currency', $this->baseCurrency);
    }
}

注册Twig扩展(方便模板调用)

创建Twig扩展,让模板可以直接使用转换功能:

// src/Twig/CurrencyExtension.php
namespace App\Twig;

use App\Service\CurrencyConverter;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class CurrencyExtension extends AbstractExtension
{
    private $converter;

    public function __construct(CurrencyConverter $converter)
    {
        $this->converter = $converter;
    }

    public function getFilters(): array
    {
        return [
            new TwigFilter('convert_currency', [$this, 'convertCurrency']),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('current_currency', [$this, 'getCurrentCurrency']),
        ];
    }

    public function convertCurrency(float $price): float
    {
        return $this->converter->convert($price);
    }

    public function getCurrentCurrency(): string
    {
        return $this->converter->getCurrentCurrency();
    }
}

之后在模板中就可以这样使用:

{# 示例:显示商品价格 #}
<p>价格: {{ product.base_price | convert_currency | number_format(2) }} {{ current_currency() }}</p>

关键注意事项

  • 路由顺序:货币切换路由必须放在业务路由前缀配置之前,避免被前缀覆盖。
  • 缓存处理:如果你的页面使用了缓存,需要把currency作为缓存键的一部分,避免不同货币版本的页面混淆。
  • 汇率更新:实际项目中不要硬编码汇率,建议定时调用API更新汇率到数据库或Redis缓存。

内容的提问来源于stack exchange,提问作者Schyzophrenic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 20:07:30