Handle Symfony 8+ strip region in RequestHeaderBasedLocaleContext by marekrzytki · Pull Request #18935 · Sylius/Sylius

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Sylius/Bundle/CoreBundle/Telemetry/EventListener/TelemetryListener.php (1)

21-21: 🛠️ Refactor suggestion | 🟠 Major

Class should be declared final.

Per coding guidelines, all PHP classes should be final except entities and repositories. TelemetryListener is a service class and should be marked as final.

Proposed fix
 /** `@internal` */
-class TelemetryListener
+final class TelemetryListener
 {

As per coding guidelines: "Use final for all PHP classes, except entities and repositories".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Sylius/Bundle/CoreBundle/Telemetry/EventListener/TelemetryListener.php`
at line 21, The TelemetryListener class should be declared final per coding
guidelines; update the class declaration for TelemetryListener by adding the
final keyword (change "class TelemetryListener" to "final class
TelemetryListener") so the service class is immutable and conforms to the rule
that non-entity/repository classes are final.
🧹 Nitpick comments (6)
src/Sylius/Bundle/LocaleBundle/tests/Context/RequestHeaderBasedLocaleContextTest.php (1)

100-145: Good coverage of macro-region locale scenarios.

The test cases cover the key scenarios for the Symfony 8+ behavior change, including en_150 and es_419 macro-region locales.

Consider adding a test case for multiple Accept-Language entries where the first language has no match but a subsequent one does, to verify the fallback iteration:

📝 Suggested additional test case
         yield 'first locale by language prefix when multiple macro region locales available' => [
             'acceptLanguage' => 'en',
             'default' => 'en_001',
             'availableLocales' => ['en_001', 'en_150'],
             'expected' => 'en_001',
         ];
+        yield 'fallback to second preferred language' => [
+            'acceptLanguage' => 'fr,en;q=0.9',
+            'default' => 'en_150',
+            'availableLocales' => ['en_150', 'de_DE'],
+            'expected' => 'en_150',
+        ];
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Sylius/Bundle/LocaleBundle/tests/Context/RequestHeaderBasedLocaleContextTest.php`
around lines 100 - 145, Add a test case to provideResolvingCases() that verifies
fallback across multiple Accept-Language entries: provide an acceptLanguage
string containing multiple comma-separated languages where the first has no
match (e.g. "fr, en" or "fr;q=0.9, en;q=0.8"), set default and availableLocales
so only the later language matches (e.g. availableLocales contains 'en_150' but
not any 'fr' locale), and assert expected resolves to the later matching locale
(e.g. 'en_150') so RequestHeaderBasedLocaleContext::provideResolvingCases covers
the iteration/fallback behavior.
src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php (1)

276-277: Consider adding a test for the minimum constraint validation.

The tests verify the default value, but there's no test asserting that values below 1000 are rejected. Following the pattern of other validation tests in this file (e.g., it_throws_an_exception_if_value_of_batch_size_is_lower_then_1), consider adding:

#[Test]
public function it_throws_an_exception_if_query_timeout_is_below_minimum(): void
{
    $this->assertConfigurationIsInvalid(
        [['telemetry' => ['query_timeout' => 999]]],
        '/Should be greater than or equal to 1000/',
        true,
    );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php`
around lines 276 - 277, Add a new unit test method named
it_throws_an_exception_if_query_timeout_is_below_minimum to
src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php
that calls assertConfigurationIsInvalid with a configuration array containing
['telemetry' => ['query_timeout' => 999]], expects the validation message
'/Should be greater than or equal to 1000/', and sets the third argument to true
(following the pattern of
it_throws_an_exception_if_value_of_batch_size_is_lower_then_1); this ensures the
telemetry -> query_timeout minimum constraint is asserted.
src/Sylius/Bundle/CoreBundle/Tests/Telemetry/Query/TimeoutRunnerTest.php (1)

154-168: Consider adding a test for query parameters passthrough.

All current tests pass empty arrays [] for parameters. Consider adding a test case that verifies non-empty parameters are correctly forwarded to the underlying connection methods.

Example test case
public function test_parameters_are_passed_through(): void
{
    $connection = $this->createMock(Connection::class);
    $connection->method('getDatabasePlatform')->willReturn($this->createMock(AbstractMySQLPlatform::class));
    $connection->expects(self::once())
        ->method('fetchAllAssociative')
        ->with(
            self::matchesRegularExpression('/MAX_EXECUTION_TIME/'),
            ['status' => 'completed'],
        )
        ->willReturn([]);

    $runner = new TimeoutRunner(5000);
    $runner->fetchAllAssociative($connection, 'SELECT * FROM orders WHERE status = :status', ['status' => 'completed']);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Sylius/Bundle/CoreBundle/Tests/Telemetry/Query/TimeoutRunnerTest.php`
around lines 154 - 168, Add a new test in TimeoutRunnerTest that verifies
non-empty query parameters are forwarded: create a Connection mock, stub
getDatabasePlatform() to return an AbstractMySQLPlatform (or MariaDBPlatform
variant as appropriate), set an expectation on the
Connection::fetchAllAssociative call to assert the SQL has the injected timeout
clause (use self::matchesRegularExpression('/MAX_EXECUTION_TIME|SET STATEMENT/')
to be flexible) and that the second argument equals the provided params array
(e.g. ['status' => 'completed']), then instantiate TimeoutRunner and call
fetchAllAssociative($connection, 'SELECT ... WHERE status = :status', ['status'
=> 'completed']) to satisfy the expectation.
UPGRADE-2.1.md (1)

1-3: Heading level should increment by one.

Line 3 uses ### (h3) directly after # (h1), skipping h2. This violates the markdown heading hierarchy rule (MD001). Consider using ## for "Telemetry improvements" or adding an intermediate h2 section.

 # UPGRADE FROM `2.1.12` TO `2.1.13`

-### Telemetry improvements
+## Telemetry improvements
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@UPGRADE-2.1.md` around lines 1 - 3, The markdown skips an H2 level by using
"### Telemetry improvements" directly under "# UPGRADE FROM `2.1.12` TO
`2.1.13`"; update the heading hierarchy by changing "### Telemetry improvements"
to "## Telemetry improvements" (or insert an intermediate H2 before it) so the
headings increment correctly and satisfy MD001; locate the "Telemetry
improvements" heading in UPGRADE-2.1.md and make the edit.
src/Sylius/Bundle/CoreBundle/Resources/config/services/telemetry/telemetry.xml (1)

210-214: Consider extracting HttpClient as a named service.

The inline HttpClient::create() factory works correctly, but extracting it as a named service would improve testability and allow easier customization (e.g., adding proxy settings or custom options).

<service id="sylius.telemetry.http_client" class="Symfony\Contracts\HttpClient\HttpClientInterface">
    <factory class="Symfony\Component\HttpClient\HttpClient" method="create" />
</service>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Sylius/Bundle/CoreBundle/Resources/config/services/telemetry/telemetry.xml`
around lines 210 - 214, Extract the inline HttpClient factory into a named
service so tests and configuration can override it: define a service id
"sylius.telemetry.http_client" using
Symfony\Component\HttpClient\HttpClient::create (service class
Symfony\Contracts\HttpClient\HttpClientInterface with factory
Symfony\Component\HttpClient\HttpClient::create) and then replace the inline
<service>...</service> argument block that currently calls HttpClient::create
with a reference to "sylius.telemetry.http_client"; ensure any places using the
inline factory (the argument passed where HttpClient is injected) now reference
the new service id so proxy/custom options can be configured in one place.
src/Sylius/Bundle/CoreBundle/Telemetry/Query/TimeoutRunner.php (1)

46-51: Add explicit return type declaration.

The fetchAssociative method has a PHPDoc return type but lacks the actual PHP return type declaration. Per coding guidelines, all methods should have type declarations.

-    public function fetchAssociative(Connection $connection, string $sql, array $params = [])
+    public function fetchAssociative(Connection $connection, string $sql, array $params = []): array|false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Sylius/Bundle/CoreBundle/Telemetry/Query/TimeoutRunner.php` around lines
46 - 51, The fetchAssociative method in TimeoutRunner lacks an explicit PHP
return type; update the method signature of fetchAssociative (which calls
applyTimeout and executeInTimeoutContext and invokes
$connection->fetchAssociative) to include the correct return type declared in
the PHPDoc (e.g., ?array or array|false as appropriate) so the signature
explicitly matches the documented return type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/OrdersBusinessDataProvider.php`:
- Around line 29-32: The constructor-promoted properties in
OrdersBusinessDataProvider::__construct (the Connection $connection and
TimeoutRunner $queryTimeoutRunner) should be declared readonly to reflect
immutability; update the promoted params to readonly Connection $connection and
readonly TimeoutRunner $queryTimeoutRunner so the class-level properties are
readonly and cannot be mutated after construction.

In `@src/Sylius/Component/Core/Telemetry/Cache/TelemetryCache.php`:
- Around line 96-99: The current separate methods wasRecentlyTriggered() and
markAsRecentlyTriggered() create a race where two processes can both think the
trigger is absent; replace them with a single atomic operation (e.g.,
acquireTrigger() or tryAcquireTrigger()) that performs a check-and-set in one
step using the cache provider's atomic primitives (or wrap the check/set in a
lock) so only the caller that successfully sets the key proceeds to send
telemetry; update callers to use the new acquire method and remove the separate
check/set usage in wasRecentlyTriggered() and markAsRecentlyTriggered().

---

Outside diff comments:
In `@src/Sylius/Bundle/CoreBundle/Telemetry/EventListener/TelemetryListener.php`:
- Line 21: The TelemetryListener class should be declared final per coding
guidelines; update the class declaration for TelemetryListener by adding the
final keyword (change "class TelemetryListener" to "final class
TelemetryListener") so the service class is immutable and conforms to the rule
that non-entity/repository classes are final.

---

Nitpick comments:
In
`@src/Sylius/Bundle/CoreBundle/Resources/config/services/telemetry/telemetry.xml`:
- Around line 210-214: Extract the inline HttpClient factory into a named
service so tests and configuration can override it: define a service id
"sylius.telemetry.http_client" using
Symfony\Component\HttpClient\HttpClient::create (service class
Symfony\Contracts\HttpClient\HttpClientInterface with factory
Symfony\Component\HttpClient\HttpClient::create) and then replace the inline
<service>...</service> argument block that currently calls HttpClient::create
with a reference to "sylius.telemetry.http_client"; ensure any places using the
inline factory (the argument passed where HttpClient is injected) now reference
the new service id so proxy/custom options can be configured in one place.

In `@src/Sylius/Bundle/CoreBundle/Telemetry/Query/TimeoutRunner.php`:
- Around line 46-51: The fetchAssociative method in TimeoutRunner lacks an
explicit PHP return type; update the method signature of fetchAssociative (which
calls applyTimeout and executeInTimeoutContext and invokes
$connection->fetchAssociative) to include the correct return type declared in
the PHPDoc (e.g., ?array or array|false as appropriate) so the signature
explicitly matches the documented return type.

In
`@src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php`:
- Around line 276-277: Add a new unit test method named
it_throws_an_exception_if_query_timeout_is_below_minimum to
src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php
that calls assertConfigurationIsInvalid with a configuration array containing
['telemetry' => ['query_timeout' => 999]], expects the validation message
'/Should be greater than or equal to 1000/', and sets the third argument to true
(following the pattern of
it_throws_an_exception_if_value_of_batch_size_is_lower_then_1); this ensures the
telemetry -> query_timeout minimum constraint is asserted.

In `@src/Sylius/Bundle/CoreBundle/Tests/Telemetry/Query/TimeoutRunnerTest.php`:
- Around line 154-168: Add a new test in TimeoutRunnerTest that verifies
non-empty query parameters are forwarded: create a Connection mock, stub
getDatabasePlatform() to return an AbstractMySQLPlatform (or MariaDBPlatform
variant as appropriate), set an expectation on the
Connection::fetchAllAssociative call to assert the SQL has the injected timeout
clause (use self::matchesRegularExpression('/MAX_EXECUTION_TIME|SET STATEMENT/')
to be flexible) and that the second argument equals the provided params array
(e.g. ['status' => 'completed']), then instantiate TimeoutRunner and call
fetchAllAssociative($connection, 'SELECT ... WHERE status = :status', ['status'
=> 'completed']) to satisfy the expectation.

In
`@src/Sylius/Bundle/LocaleBundle/tests/Context/RequestHeaderBasedLocaleContextTest.php`:
- Around line 100-145: Add a test case to provideResolvingCases() that verifies
fallback across multiple Accept-Language entries: provide an acceptLanguage
string containing multiple comma-separated languages where the first has no
match (e.g. "fr, en" or "fr;q=0.9, en;q=0.8"), set default and availableLocales
so only the later language matches (e.g. availableLocales contains 'en_150' but
not any 'fr' locale), and assert expected resolves to the later matching locale
(e.g. 'en_150') so RequestHeaderBasedLocaleContext::provideResolvingCases covers
the iteration/fallback behavior.

In `@UPGRADE-2.1.md`:
- Around line 1-3: The markdown skips an H2 level by using "### Telemetry
improvements" directly under "# UPGRADE FROM `2.1.12` TO `2.1.13`"; update the
heading hierarchy by changing "### Telemetry improvements" to "## Telemetry
improvements" (or insert an intermediate H2 before it) so the headings increment
correctly and satisfy MD001; locate the "Telemetry improvements" heading in
UPGRADE-2.1.md and make the edit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07c8438d-1a1c-469b-b7cb-369179b1fc23

📥 Commits

Reviewing files that changed from the base of the PR and between 385a5e5 and 45bc3da.

📒 Files selected for processing (30)
  • CHANGELOG-2.2.md
  • UPGRADE-2.1.md
  • composer.json
  • src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php
  • src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php
  • src/Sylius/Bundle/CoreBundle/Resources/config/services/telemetry/telemetry.xml
  • src/Sylius/Bundle/CoreBundle/SyliusCoreBundle.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/EventListener/TelemetryListener.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/CountriesDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/CurrenciesDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/LocalesDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/MetricsCountsDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/OrdersBusinessDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/PaymentMethodsDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Provider/Business/ShippingMethodsDataProvider.php
  • src/Sylius/Bundle/CoreBundle/Telemetry/Query/TimeoutRunner.php
  • src/Sylius/Bundle/CoreBundle/Tests/Telemetry/Query/TimeoutRunnerTest.php
  • src/Sylius/Bundle/CoreBundle/tests/DependencyInjection/ConfigurationTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/EventListener/TelemetryListenerTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/CountriesDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/CurrenciesDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/LocalesDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/MetricsCountsDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/OrdersBusinessDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/PaymentMethodsDataProviderTest.php
  • src/Sylius/Bundle/CoreBundle/tests/Telemetry/Provider/Business/ShippingMethodsDataProviderTest.php
  • src/Sylius/Bundle/LocaleBundle/Context/RequestHeaderBasedLocaleContext.php
  • src/Sylius/Bundle/LocaleBundle/tests/Context/RequestHeaderBasedLocaleContextTest.php
  • src/Sylius/Component/Core/Telemetry/Cache/TelemetryCache.php
  • src/Sylius/Component/Core/Telemetry/Cache/TelemetryCacheInterface.php