Repository navigation
Code review findings: leftover resolver.xml, CachedVatContext reset, and other issues #5
Description
A code review of the plugin (master, 209a7c2) surfaced the findings below. Overall the plugin is in good shape — clean decorator/composite architecture, solid test coverage, psalm level 1, mutation testing — but there is one genuine leftover bug, one runtime hazard, and a handful of lower-severity issues.
Bugs
-
resolver.xmlregisters services for classes that no longer exist.src/Resources/config/services/resolver.xmldefinesSetono\SyliusToggleVatPlugin\Resolver\PriceResolverandPriceResolverInterface, but there is nosrc/Resolver/directory — the classes were deleted in 198aa20 ("Decorate calculator instead of PriceHelper") while the XML file and its import inservices.xmlwere left behind. It only works today because both services are private and unused, so the compiler drops them beforelint:containerruns. Anyone injectingPriceResolverInterface(it looks like a public API) gets a confusing "class not found" at runtime. Fix: delete the file and its import. -
CachedVatContextcaches request state for the service's lifetime with no reset.src/Context/CachedVatContext.phpmemoizes the first answer forever. Under PHP-FPM that effectively means "per request", but under worker-mode runtimes (FrankenPHP, RoadRunner, Swoole) the first visitor's VAT choice sticks to the worker and is served to every subsequent visitor — and the toggle appears broken. Fix: implementSymfony\Contracts\Service\ResetInterfaceand tag the service withkernel.reset. -
Loose cookie parsing.
CookieBasedVatContextdoes(bool) $cookie, so any non-empty value other than"0"— including"false"— means "with VAT". Fix: accept only'1'/'0'and throwNoVatContextExceptionfor anything else, which cleanly falls through to the configured default. -
Defensive rounding in the calculator.
ProductVariantPricesCalculatorcasts the tax calculator'sfloattoint, which truncates. Sylius'sDefaultCalculatorhappens toround()before returning, so stock installs are fine — but theCalculatorInterfacecontract just saysfloat, and a custom/decorated tax calculator returning e.g.219.8would silently shave a cent. Fix:(int) round(...).
Security (low severity)
-
State change via GET. The toggle route is a plain
<a href>in the topbar on every page, so crawlers and link prefetchers will follow it, and it is trivially cross-site triggerable (<img src=".../toggle-vat">). Impact is only a flipped display preference. Fix: at minimum addrel="nofollow"to the link; a small POST form would be the proper fix. -
Referer-based redirect without validation.
ToggleVatActionredirects to theRefererheader verbatim. Practically unexploitable, but security scanners will flag it as an open redirect. Fix: only redirect when the referer host matches the request host. -
Document the caching caveat. Prices now vary by cookie, so a full-page cache/CDN in front of the shop will serve the wrong VAT variant between users unless it varies on this cookie. Worth a README warning.
Architecture
-
sylius/core: ^1.0promises far more than is true. The plugin depends on thesylius.calculator.product_variant_priceandsylius.twig.extension.priceservice ids, the concreteSylius\Bundle\CoreBundle\Twig\PriceExtensionshape, andProductVariantPricesCalculatorInterface::calculateOriginal— none of which existed across the whole 1.x line, and CI only tests~1.12.19. Fix: tighten to^1.12so incompatible installs fail atcomposer requireinstead of at runtime. -
Custom VAT contexts silently defeat the toggle. Autoconfigured contexts get tag priority 0, which outranks
CookieBasedVatContext(-90) — so a custom context that always returns makes the toggle button a no-op (it sets a cookie that is never consulted). If intended, document the -90/-100 priorities in the README's "VAT context" section so implementors can slot around them. -
Twig\PriceExtensiondecoration is quietly fragile. The wrappingnew TwigFilter($name, $closure)drops any options the original filter carries (currently none, so no bug today), and__callnever fires for the extension interface methods (getFunctions()etc.) becauseAbstractExtensiondefines them — if Sylius's base extension ever adds functions or filter options, the decorator silently swallows them. A comment or a hard-coded wrap of the two known filter names would make the intent and risk explicit.
Nitpicks / leftovers
-
SetonoSyliusToggleVatExtension— the@vardocblock still listsdecorate_price_helper: bool, an option that no longer exists. -
Configuration— the builder chain is missing its trailing->end()calls aftercookie_name. It works (nodes are appended on creation), but breaks the moment someone appends another node to the chain. -
Twig\Extension—sstv_display_with_vatis registered withneeds_environment => true, butRuntime::displayWithVat()takes noEnvironment; it only works because PHP ignores extra args.is_safe => ['html']on a bool-returning function is also meaningless. Both options should be dropped (they are correct onsstv_vat_toggler). -
ProductVariantPricesCalculator— stray unused@psalm-type BacktraceClosure. -
behat/behatis in require-dev but there are no Behat features/suites.
Activity
loevgaard commentedon Aug 23, 2026
Opened a PR per finding. Most branch off
masterindependently; three are stacked where two changes touch the same file, noted below.Bugs
Security
Architecture
Nitpicks
decorate_price_helperkey from the config shapesstv_display_with_vatmasterhas been failing since #4 (2026-08-19) with the same error, and it is not related to any of these changes:Composer now refuses to install packages with open advisories (
policy.advisories.block, on by default), andsylius/sylius ~1.12.19pullsapi-platform/core ^2.6, whose 2.x line has unfixed advisories and is EOL. Every job that runscomposer installdies at that step, so nothing downstream even executes.It only affects
require-dev— the test application, not anything the plugin ships. Options as I see them, none of which I wanted to pick unilaterally since it is a security-policy call:config.policy.advisories.ignore-id— narrow, but it does mute real advisories for the test app.policy.advisories.block: false— blunter version of the same.Happy to send whichever you prefer as its own PR. Until then the checks on all 15 PRs are red for reasons unrelated to their contents — each was verified locally against PHPUnit, ECS and Psalm before pushing.