webmozarts / assert

Assertions to validate method input/output with nice error messages.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Generically support 'not', 'and', 'or'

TiMESPLiNTER opened this issue · comments

I need to assert the following:

Assert::string($value) || Assert::isInstanceOf($value, ClassA::class);

Maybe it's not the kind of problem this lib wants to solve. But it would be really great to have something like an or to chain multiple assertions.

Any opinion about it? Or maybe it's already possible I didn't got it yet?

Something like this would solve it:

try {
    Assert::string($value);
} catch (InvalidArgumentException $e) {
    Assert::isInstanceOf($value, ClassA::class);
}

but that's definitely not the way to go, right? 😅

Something like this would be nice:

Assert::isInstanceOfAny($value, [Type::string(), Type::object(ClassA::class)]);

I'd love a way to 'generically' support 'not', 'or' and even 'and' types of assertions, but thats currently not possible with this package. I'd love to see ideas that would make this possible though.

I could hack it like this:

Assert::oneOf(
  function () use ($value) {
    Assert::string($value)
  }, 
  Assert::not(
    function () use ($value) {
      Assert::isInstanceOf($value, ClassA::class));
    }
  )
);
class Assert
{
  public static function oneOf(...$asserts): void
  {
    $exceptionCount = 0;

    foreach ($asserts as $assert) {
      try {
        $assert();
      } catch (Exception $e) {
        ++$exceptionCount;
      }
    }

    if (count($asserts) === $exceptionCount) {
      throw new InvalidArgumentException('Value passed no asserts');
    }
  }

  // This is the same as just call all the asserts after each other
  // like you can do already
  public static function allOf(...$asserts): void
  {
    foreach ($asserts as $assert) {
      $assert();
    }
  }

  public static function not($assert): \Closure
  {
    return function () use ($assert) {
        try {
            $assert();
        } catch (Exception $e) {
            return;
        }

        throw new InvalidArgumentException();
    }
  }
}

And I know it's a terrible solution. The problem is that we need to prevent the immediate execution of the asserts and stack them somehow.

Related: #129

Related: #129, #169 , #173

In #173, @BackEndTea gave a proposed implementation where the checks were (presumably) moved to a separate class that just returns booleans. Then the Assert class can throw exceptions based on those return values.
This also allows supporting not, and and or as the checks can be executed without throwing an Exception.

Also, this would grant @ribeiropaulor's wish (in #129) to have a class that just returns booleans.

This should be possible without breaking BC.