← Alle Beiträge

Shopware 6: „Input value \"p\" is invalid and flag \"FILTER_NULL_ON_FAILURE\" was not set“ einordnen und abstellen

Shopware 6: „Input value \"p\" is invalid and flag \"FILTER_NULL_ON_FAILURE\" was not set“ einordnen und abstellen

Der Shop läuft, die Kunden bestellen, und trotzdem zeigt das Error-Log Tausende Einträge. Fast alle sehen gleich aus: Input value "p" is invalid. Es ist einer der häufigsten Fehler in Shopware-6.6- und 6.7-Logs. Die gute Nachricht vorweg: Kaputt ist hier meistens nichts. Die schlechte: Der Lärm versteckt die Fehler, die wirklich zählen.

Die Fehlermeldung

request.ERROR: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
"Input value "p" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set." at HttpKernel.php line 83
[previous exception] Symfony\Component\HttpFoundation\Exception\BadRequestException

Seltener taucht dieselbe Ursache in einer zweiten Form auf, nämlich wenn ein CMS-Produktslider die Seite rendert:

Error when loading variants in ProductSliderCmsElementResolver:
Input value "p" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.
#0 vendor/symfony/http-foundation/ParameterBag.php(166): Symfony\Component\HttpFoundation\InputBag->filter()
#1 vendor/shopware/core/Content/Product/SalesChannel/Listing/Processor/PagingListingProcessor.php(68): Symfony\Component\HttpFoundation\ParameterBag->getInt()

Was dahintersteckt

p ist in Shopware der Parameter für die Seitenzahl in Produktlistings, also ?p=2 für Seite zwei. Der PagingListingProcessor liest ihn so:

$page = $request->query->has('p') ? $request->query->getInt('p') : null;

getInt() prüft den Wert mit FILTER_VALIDATE_INT. Bis Symfony 6.2 wurde ein ungültiger Wert dabei still zu 0 gemacht. Symfony 6.3 hat das als deprecated markiert, und seit Symfony 7.0 wirft getInt() eine Exception. So steht es im Changelog der HttpFoundation-Komponente. Bei Query-Parametern ist das ein InputBag, und der wirft eine BadRequestException. Der HttpKernel wandelt sie in eine BadRequestHttpException um, der Client bekommt einen 400er.

Shopware 6.5 setzte noch auf symfony/http-foundation 6.x, Shopware 6.6 steigt auf ~7.0 um. Deshalb siehst du den Fehler typischerweise erst nach dem Update auf 6.6. Auch auf dem aktuellen trunk liest der PagingListingProcessor p und limit noch mit getInt(). Einen Shopware-Fix gibt es also nicht, das Verhalten ist gewollt.

Was zählt als ungültig? Wir haben es mit filter_var() nachgestellt: abc, 2abc, 1.5, 2/ und auch ein leeres ?p= fallen durch. 2, -1 und 2 mit Leerzeichen gehen durch. Ein Array wie ?p[]=1 löst übrigens eine andere Meldung aus (contains an array, but "FILTER_REQUIRE_ARRAY" ...), das ist ein eigener Fall.

Wer schickt so etwas? Vor allem Bots. Darunter SQL-Injection-Scanner, die Werte wie p=(select(0)from(select(sleep(... an Kategorie-URLs hängen. Dazu Crawler mit kaputten Pagination-Links, Werbe-Tracking, das Parameter aneinanderklebt, und alte Links aus Feeds.

Prüfen, ob es wirklich das ist

Bevor du etwas wegfilterst, schau dir an, woher die Requests kommen. Im Access-Log:

grep -E '[?&]p=([^& ]*[^0-9& ]|&| )' /var/log/nginx/access.log | awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -20

Wenn dort eine Handvoll IPs mit absurden Werten auftauchen, ist es Bot-Verkehr. Dann reicht der Filter weiter unten.

Anders sieht es aus, wenn echte Besucher dabei sind und die URLs aus deinem eigenen Shop stammen. Dann ist ein Link irgendwo kaputt. Typische Kandidaten sind ein Theme-Template, das ?p= ohne Wert baut, ein Filter-Plugin, das die Seitenzahl falsch weiterreicht, oder ein Redirect, der den Query-String doppelt anhängt (dann siehst du Werte wie 2?p=3). Das findest du über den Referer im Access-Log. Repariere in dem Fall die Quelle, ein Filter würde nur die Symptome verstecken.

Die Lösung

Für Bot-Verkehr ist die einfachste Lösung, den Request vor PHP abzuweisen. Das spart außerdem einen kompletten Shopware-Bootstrap pro Scanner-Request.

nginx, im server-Block:

# Seitenzahl muss numerisch sein, sonst 400 ohne PHP
if ($arg_p ~ "[^0-9]") {
    return 400;
}

$arg_p ist der rohe, URL-kodierte Wert. Ein %28select... enthält also ein % und greift auch. Ein leeres ?p= lässt die Regel durch, weil nginx „leer" und „nicht vorhanden" hier nicht unterscheidet. Das bleibt also im Log, ist aber in der Praxis selten.

Apache, in der .htaccess in public/ vor den Shopware-Regeln:

RewriteCond %{QUERY_STRING} (^|&)p=[^&]*[^0-9&]
RewriteRule ^ - [R=400,L]

Laut Apache-Doku liefert R mit einem Status außerhalb von 300–399 einfach diesen Status aus, ohne Weiterleitung. Den Parameter limit liest Shopware übrigens genauso. Wenn du dort ähnliche Einträge siehst, ergänze die Regel entsprechend.

Wenn du nicht an die Webserver-Konfiguration kommst, zum Beispiel beim Managed Hosting, bleibt die Symfony-Seite. Über framework.exceptions kannst du (seit Symfony 5.4) das Log-Level für eine Exception-Klasse senken:

# config/packages/exceptions.yaml
framework:
    exceptions:
        Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
            log_level: notice

Ehrlich gesagt ist das ein Workaround mit Nebenwirkungen. Die Einstellung greift für alle 400er, auch für „The JSON payload is malformed" aus der Admin-API oder Fehler einer Integration, die dich eigentlich interessieren. Shopwares eigenes shopware.logger.exclude_exception hilft hier auch nicht weiter. Es vergleicht den exakten Klassennamen und würde damit ebenfalls jeden 400er verschlucken.

Wenn das nicht hilft

Taucht der Fehler weiter auf, obwohl der Webserver-Filter aktiv ist, kommen die Requests vermutlich gar nicht über den Webserver, den du konfiguriert hast. Ein vorgeschalteter Reverse Proxy, ein CDN oder ein zweiter vHost für eine andere Domain sind die üblichen Verdächtigen.

Kommt die Meldung aus dem ProductSliderCmsElementResolver, steckt derselbe Parameter dahinter. Ein Produktslider auf einer Kategorie- oder Erlebniswelt-Seite liest die Seitenzahl aus dem aktuellen Request mit. Der Filter oben deckt den Fall mit ab.

Um diese Meldung kümmert sich kaum jemand. Verständlich, denn sie tut nicht weh. Aber wer bei Tausenden harmlosen Einträgen im Log nach dem einen echten Checkout-Fehler sucht, sucht lange. ShopSignal zählt Errors und Warnings pro Shop, macht die Logs per Volltextsuche durchsuchbar und schlägt über Alert-Regeln an, wenn die Zahlen ausreißen. So fällt dir auf, wenn aus dem Grundrauschen plötzlich etwas Neues wird.