Repository: symfony/symfony
File: src/Symfony/Component/HttpFoundation/AcceptHeader.php (PHP)

Code before:
                    return $a->getIndex() > $b->getIndex() ? 1 : -1;
                }

                return $qA > $qB ? -1 : 1;
            });

            $this->sorted = true;
        }
    }

    /**
     * Generates the canonical key for storing/retrieving an item.
     */
    private function getCanonicalKey(AcceptHeaderItem $item): string
    {
        $parts = [];

        foreach ($this->getMediaParams($item) as $name => $value) {
            if (null === $value) {
                $parts[] = $name; // Flag parameter (e.g., "flowed")
                continue;
            }

            // Quote values containing spaces, commas, semicolons, or equals per RFC 9110
            // This handles cases like 'format="value with space"' or similar.
            $quotedValue = (\is_string($value) && preg_match('/[\s;,=]/', $value))
                ? '"'.addcslashes($value, '"\\').'"'
                : $value;

            $parts[] = $name.'='.$quotedValue;
        }

        return $item->getValue().($parts ? ';'.implode(';', $parts) : '');
    }

    /**
     * Sorts candidates by specificity (desc), quality (desc), and index (asc).
     *
     * @param AcceptHeaderItem[] $candidates
     */
    private function sortCandidates(array &$candidates, AcceptHeaderItem $queryItem): void
    {
        usort($candidates, fn (AcceptHeaderItem $a, AcceptHeaderItem $b) => $this->getSpecificity($b, $queryItem) <=> $this->getSpecificity($a, $queryItem) // Descending specificity
                ?: $b->getQuality() <=> $a->getQuality() // Descending quality
                ?: $a->getIndex() <=> $b->getIndex() // Ascending index (stability)
        );
    }

    /**
     * Checks if a given header item (range) matches a queried item (value).
     *

Review comment (suggestion):
```suggestion
            $quotedValue = (\is_string($value) && 1 === preg_match('/[\s;,=]/', $value))
```

Code after:
            $this->sorted = true;
        }
    }

    /**
     * Generates the canonical key for storing/retrieving an item.
     */
    private function getCanonicalKey(AcceptHeaderItem $item): string
    {
        $parts = [];

        // Normalize and sort attributes for consistent key generation
        $attributes = $this->getMediaParams($item);
        ksort($attributes);

        foreach ($attributes as $name => $value) {
            if (null === $value) {
                $parts[] = $name; // Flag parameter (e.g., "flowed")
                continue;
            }

            // Quote values containing spaces, commas, semicolons, or equals per RFC 9110
            // This handles cases like 'format="value with space"' or similar.
            $quotedValue = \is_string($value) && preg_match('/[\s;,=]/', $value) ? '"'.addcslashes($value, '"\\').'"' : $value;

            $parts[] = $name.'='.$quotedValue;
        }

        return $item->getValue().($parts ? ';'.implode(';', $parts) : '');
    }

    /**
     * Checks if a given header item (range) matches a queried item (value).
     *
     * @param AcceptHeaderItem $rangeItem The item from the Accept header (e.g., text/*;format=flowed)
     * @param AcceptHeaderItem $queryItem The item being queried (e.g., text/plain;format=flowed;charset=utf-8)
     */
    private function matches(AcceptHeaderItem $rangeItem, AcceptHeaderItem $queryItem): bool
    {
        $rangeValue = strtolower($rangeItem->getValue());
        $queryValue = strtolower($queryItem->getValue());

        // Handle universal wildcard ranges
        if ('*' === $rangeValue || '*/*' === $rangeValue) {
            return $this->rangeParametersMatch($rangeItem, $queryItem);
        }

        // Queries for '*' only match wildcard ranges (handled above)
        if ('*' === $queryValue) {
            return false;
        }