Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to type-hint EscaperRuntime::escape() #4533

Open
smnandre opened this issue Jan 5, 2025 · 0 comments
Open

Attempt to type-hint EscaperRuntime::escape() #4533

smnandre opened this issue Jan 5, 2025 · 0 comments

Comments

@smnandre
Copy link
Contributor

smnandre commented Jan 5, 2025

Hi, i tried to see if we could type-hint the return value of the escaper.

PHPStan Playground: https://phpstan.org/r/3028ae7d-3452-4558-9249-60a0bb60c44a

Tip

I think we cover all "basic" use-cases: int, float, null, string, Markup and Stringable objects.

/**
 * @template TOther of mixed
 *
 * @phpstan-return ($string is string ? string : ($string is \Stringable ? ($autoescape is true ? string : TOther) : TOther ) )
 */
public static function escape($string, string $strategy = 'html', bool $autoescape = false): mixed

I'm not a PHPStan expert(far from it to be honest), so please tell me if something is odd. Or if adding this would add no real value :)

Also: I had to recreate a similar-ish simplified RuntimeEscaper class and algorithm to play in the Playground, but it does return the same things -- at least for the considered values.

Result

Checkbox "Treat PHPDoc types as certain" enabled.

Line in RED when the annotation produces the expected effet (PHPStan consider the test obvious)

image

Source

Code here also for backup
<?php declare(strict_types = 1);

class Markup implements \Stringable 
{
	public function __toString(): string
	{
		return 'abc';
	}
}

$stringable = new class implements \Stringable {
	public function __toString():string
	{
		return '';
	}
};

class EscaperRuntime
{
    /**
     * @template TOther of mixed
     * 
     * Escapes a string.
     *
     * @param string|TOther   $string     The value to be escaped
     * @param bool        $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
     *
     * @phpstan-return ($string is string ? string : ($string is \Stringable ? ($autoescape is true ? string : TOther) : TOther ) )
     * @return string|mixed The escaped value
	 */
	public static function escape($string, string $strategy = 'html', bool $autoescape = false): mixed
	{
		if ($autoescape && $string instanceof Markup) {
            return $string;
        }

		if (!is_string($string)) {
            if ($string instanceof \Stringable) {
				if ($autoescape) {
					if (is_a($string, Markup::class)) {
						return (string) $string;
					}
				}

				$string = (string) $string;	
			} elseif ('html' === $strategy) {
                return $string;				
			}
		}

		if ('html' === $strategy) {
			if (is_string($string)) {
                return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', false);
			}
			throw new TypeError();
		}

		throw new LogicException('AAA');
	}
}

$strategy = (0 === rand(0, 1)) ? 'html' : 'ooo';

$checks = [
	// Scalar + null
	is_null(EscaperRuntime::escape(null, $strategy)),
	is_null(EscaperRuntime::escape(null, $strategy, true)),
	is_int(EscaperRuntime::escape(0, $strategy)),
	is_int(EscaperRuntime::escape(0, $strategy, true)),
	is_float(EscaperRuntime::escape(0.0, $strategy)),
	is_float(EscaperRuntime::escape(0.0, $strategy, true)),
	is_string(EscaperRuntime::escape('', $strategy)),	
	is_string(EscaperRuntime::escape('', $strategy, true)),
	// Markup or Stringable
	EscaperRuntime::escape(new Markup(), $strategy) instanceof Markup,
	is_string(EscaperRuntime::escape(new Markup(), $strategy, true)),
	EscaperRuntime::escape($stringable, $strategy)::class === $stringable::class,
	is_string(EscaperRuntime::escape($stringable, $strategy, true)),

    // Other types, autoescape FALSE  =>  Not a string (stdClass)
	!is_string(EscaperRuntime::escape(new \stdClass, $strategy)),
	// Other types, autoescape TRUE   =>  Unpredictable
	is_string(EscaperRuntime::escape(new \stdClass, $strategy, true)),
];

var_dump($checks);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant