Skip to content

Multi-layer encoding #294

Description

@theofidry

When calling assert like so:

Assert::count(..., message: $message);

$message is forwarded to sprintf. As a result if you are to do something like this:

Assert::count(
    ...,
    message: 'This is my custom message %s', // <- the placeholder will be filled up automatically, great!
);

Which does mean if you are using your own sprintf you should escape it first:

Assert::count(
    ...,
    message: 'This is my %%w custom message %s', // <- manually escape "%w" as this would make sprintf fail (it is an invalid format specifier)
);

// Automatic escaping:

Assert::count(
    ...,
    message: str_replace('%', '%%', 'This is my %%w custom message %s'),
);

So far so good. Now the problem: this does not work for all assertions. Indeed the example above with Assert::count() will still result in ValueError: Unknown format specifier "w".

The root of the issue is that an assertion can rely on other assertions internally, e.g. ::count() calls ::eq(). So the custom escaped message becomes unescaped when passing it to ::eq(). Making it work requires the user to find out how many items the string is evaluated to escape it accordingly (in this example twice).

I think this is a design flaw and the way it could be fixed is to escape the message passed within Assert when relying on inner assertions:

// Assert.php

public static function count($array, $number, $message = '')
    {
        static::eq(
            \count($array),
            $number,
            \sprintf(
                self::sprintfEscape($message) ?: 'Expected an array to contain %d elements. Got: %d.',
                $number,
                \count($array)
            )
        );
    }
    
    public static function sprintfEscape(string $value): string {
      return str_replace('%', '%%', $value);
    }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions