assert icon indicating copy to clipboard operation
assert copied to clipboard

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

Open TiMESPLiNTER opened this issue 5 years ago • 4 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)]);

TiMESPLiNTER avatar Apr 05 '20 08:04 TiMESPLiNTER

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.

BackEndTea avatar Apr 19 '20 08:04 BackEndTea

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.

TiMESPLiNTER avatar Apr 21 '20 06:04 TiMESPLiNTER

Related: https://github.com/webmozart/assert/issues/129

zerkms avatar May 30 '20 11:05 zerkms

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.

FrontEndCoffee avatar Aug 25 '20 06:08 FrontEndCoffee