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

Code before:
 * file that was distributed with this source code.
 */

namespace Symfony\Component\HtmlSanitizer;

use Symfony\Component\HtmlSanitizer\Parser\MastermindsParser;
use Symfony\Component\HtmlSanitizer\Parser\NativeParser;
use Symfony\Component\HtmlSanitizer\Parser\ParserInterface;
use Symfony\Component\HtmlSanitizer\Reference\W3CReference;
use Symfony\Component\HtmlSanitizer\TextSanitizer\StringSanitizer;
use Symfony\Component\HtmlSanitizer\Visitor\DomVisitor;

/**
 * @author Titouan Galopin <galopintitouan@gmail.com>
 */
final class HtmlSanitizer implements HtmlSanitizerInterface
{
    private \Closure $parser;

    /**
     * @var array<string, DomVisitor>
     */
    private array $domVisitors = [];

    /**
     * @param \Closure(string):\Dom\Node|\DOMNode|null $parser
     */
    public function __construct(
        private HtmlSanitizerConfig $config,
        ParserInterface|\Closure|null $parser = null,
    ) {
        $parser ??= (\PHP_VERSION_ID < 80400 ? new MastermindsParser() : new NativeParser())->parse(...);
        $this->config = $config;
        $this->parser = $parser instanceof ParserInterface ? $parser->parse(...) : $parser;
    }

    public function sanitize(string $input): string
    {
        return $this->sanitizeWithContext(W3CReference::CONTEXT_BODY, $input);
    }

    public function sanitizeFor(string $element, string $input): string
    {
        return $this->sanitizeWithContext(
            W3CReference::CONTEXTS_MAP[StringSanitizer::htmlLower($element)] ?? W3CReference::CONTEXT_BODY,
            $input
        );
    }

    private function sanitizeWithContext(string $context, string $input): string
    {

Review comment (suggestion):
Should you update the signature as you call the closure with 2 parameters? But `ParserInterface::parse()` only takes one.
```suggestion
     * @param \Closure(string, string):\Dom\Node|\DOMNode|null $parser
```

There would be no ambiguity with an interface.

Code after:
 * file that was distributed with this source code.
 */

namespace Symfony\Component\HtmlSanitizer;

use Symfony\Component\HtmlSanitizer\Parser\MastermindsParser;
use Symfony\Component\HtmlSanitizer\Parser\NativeParser;
use Symfony\Component\HtmlSanitizer\Parser\ParserInterface;
use Symfony\Component\HtmlSanitizer\Reference\W3CReference;
use Symfony\Component\HtmlSanitizer\TextSanitizer\StringSanitizer;
use Symfony\Component\HtmlSanitizer\Visitor\DomVisitor;

/**
 * @author Titouan Galopin <galopintitouan@gmail.com>
 */
final class HtmlSanitizer implements HtmlSanitizerInterface
{
    private ParserInterface $parser;

    /**
     * @var array<string, DomVisitor>
     */
    private array $domVisitors = [];

    public function __construct(
        private HtmlSanitizerConfig $config,
        ?ParserInterface $parser = null,
    ) {
        $this->config = $config;
        $this->parser = $parser ?? (\PHP_VERSION_ID < 80400 ? new MastermindsParser() : new NativeParser());
    }

    public function sanitize(string $input): string
    {
        return $this->sanitizeFor(W3CReference::CONTEXT_BODY, $input);
    }

    public function sanitizeFor(string $element, string $input): string
    {
        $element = StringSanitizer::htmlLower($element);
        $context = W3CReference::CONTEXTS_MAP[$element] ?? W3CReference::CONTEXT_BODY;
        $element = isset(W3CReference::BODY_ELEMENTS[$element]) ? $element : $context;

        // Text context: early return with HTML encoding
        if (W3CReference::CONTEXT_TEXT === $context) {
            return StringSanitizer::encodeHtmlEntities($input);
        }

        // Other context: build a DOM visitor
        $this->domVisitors[$context] ??= $this->createDomVisitorForContext($context);