Skip to content

Instantly share code, notes, and snippets.

@oplanre
Created November 2, 2024 00:13
Show Gist options
  • Save oplanre/f54e3bf71f90844c00675b835e4cb4ef to your computer and use it in GitHub Desktop.
Save oplanre/f54e3bf71f90844c00675b835e4cb4ef to your computer and use it in GitHub Desktop.
Assertion class PHP
<?php
class Assert
{
protected function __construct(
) {}
protected static function expect(
mixed $value,
bool $invert,
bool $condition,
string $message,
mixed $expectation = null,
bool $be = true,
): void {
if ($condition === $invert) {
$not = $invert ? "not " : "";
$value = Dumper::inline($value);
$expectation = null !== $expectation ? Dumper::inline($expectation) : "";
$be = $be ? "to be" : "to";
throw new AssertionError("\033[1;35mExpected {$value} \033[1;35m {$be} {$not}{$message} \033[0m{$expectation}");
}
}
private static function allKeysExist(array $keys, array $array): bool
{
foreach ($keys as $key) {
if (!array_key_exists($key, $array)) {
return false;
}
}
return true;
}
private static function allValuesExist(array $values, array $array): bool
{
foreach ($values as $value) {
if (!in_array($value, $array, true)) {
return false;
}
}
return true;
}
private static function instanceOfAny(mixed $value, array $classes): array
{
$msg = "an instance of any of " . implode(', ', array_map(fn($class) => Dumper::dumpClassName($class), $classes));
foreach ($classes as $class) {
if ($value instanceof $class) {
return [true, $msg];
}
}
return [false, $msg];
}
private static function throw(mixed $value, string|object|null $exceptionClass = null, ?string $message = null): array
{
try {
call_user_func($value);
} catch (\Throwable $e) {
if (is_object($exceptionClass))
$exceptionClass = get_class($exceptionClass);
if ($exceptionClass && !($e instanceof $exceptionClass)) {
return [false, "to throw an exception of type " . Dumper::dumpClassName($exceptionClass), false];
}
if ($message && $e->getMessage() !== $message) {
return [false, "to throw an exception with message " . Dumper::inline($message), false];
}
return [true, "to throw an exception", false];
}
return [false, "to throw an exception", false];
}
protected static function hasHave(string $tag, mixed $pre, mixed $value, array $args): array
{
$expectation = $args[0] ?? null;
$propertyValue = $args[1] ?? null;
return [
match ($str = substr($tag, strlen($pre))) {
'key' => is_array($value) && array_key_exists($expectation, $value),
'property' => is_object($value) && property_exists($value, $expectation) && ($propertyValue === null || $value->$expectation === $propertyValue),
'method' => is_object($value) && method_exists($value, $expectation),
'count' => is_countable($value) && count($value) === $expectation,
'length' => (is_array($value) && (count($value) === $expectation))
|| (is_string($value) && (strlen($value) === $expectation))
},
"have {$str}",
false,
];
}
protected static function impl(string $tag, mixed $value, mixed ...$args): array
{
$expectation = $args[0] ?? null;
return match ($tag) {
'null' => [is_null($value), 'null'],
'nan' => [is_double($value) && is_nan($value), 'NaN'],
'countable' => [is_countable($value), 'countable'],
'truthy' => [(bool) $value, 'truthy'],
'falsy' => [!(bool) $value, 'falsy'],
'true' => [$value === true, 'true'],
'false' => [$value === false, 'false'],
'array' => [is_array($value), 'an array'],
'int' => [is_int($value), 'an int'],
'object' => [is_object($value), 'an object'],
'string' => [is_string($value), 'a string'],
'json' => [is_string($value) && json_decode($value) !== null, 'a valid JSON string'],
'float' => [is_float($value), 'a float'],
'bool' => [is_bool($value), 'a bool'],
'number' => [is_double($value) || is_int($value), 'a number'],
'numeric' => [is_numeric($value), 'a number or numeric string'],
'resource' => [is_resource($value), 'a resource'],
'callable' => [is_callable($value), 'a callable'],
'iterable' => [is_iterable($value), 'iterable'],
'scalar' => [is_scalar($value), 'a scalar'],
'link' => [is_string($value) && is_link($value), 'a link'],
'file' => [is_string($value) && is_file($value), 'a file'],
'directory' => [is_string($value) && is_dir($value), 'a directory'],
'writable' => [is_string($value) && is_writable($value), 'writable'],
'readable' => [is_string($value) && is_readable($value), 'readable'],
'readablefile' => [is_string($value) && is_file($value) && is_readable($value), 'a readable file'],
'writablefile' => [is_string($value) && is_file($value) && is_writable($value), 'a writable file'],
'readabledirectory' => [is_string($value) && is_dir($value) && is_readable($value), 'a readable directory'],
'writabledirectory' => [is_string($value) && is_dir($value) && is_writable($value), 'a writable directory'],
'readablelink' => [is_string($value) && is_dir($value) && is_readable($value), 'a readable link'],
'writablelink' => [is_string($value) && is_dir($value) && is_writable($value), 'a writable link'],
'stream' => [is_resource($value) && get_resource_type($value) === 'stream', 'a stream'],
'arrayorobject' => [is_array($value) || is_object($value), 'an array or object'],
'empty' => [empty($value), "empty"],
'is', 'be' => [$value === $expectation, "identical to"],
'equal' => [$value == $expectation, "equal to"],
'same' => [$value === $expectation, "the same as"],
'greater', 'greaterthan' => [$value > $expectation, "greater than"],
'less', 'lessthan' => [$value < $expectation, "less than"],
'greaterorequal',
'greaterthanorequalto' => [$value >= $expectation, "greater than or equal to"],
'lessorequal',
'lessthanorequalto' => [$value <= $expectation, "less than or equal to"],
'type' => [gettype($value) === $expectation, "of type"],
'instance', 'instanceof' => [is_object($value) && $value instanceof $expectation, "an instance of"],
'includeallkeys',
'includesallkeys' => [(is_array($value) || is_object($value)) && static::allKeysExist($expectation, (array) $value), "include all keys", false],
'includeallvalues',
'includesallvalues' => [(is_array($value) || is_object($value)) && static::allValuesExist($expectation, (array) $value), "include all values", false],
'matches', 'match' => [is_string($value) && preg_match($expectation, $value), "match", false],
'startwith', 'startswith' => [is_string($value) && strpos($value, $expectation) === 0, "start with", false],
'endwith', 'endswith' => [is_string($value) && str_ends_with($value, $expectation), "end with", false],
'throw', 'throws', 'throwexception' => static::throw($value, $expectation, $args[1] ?? null),
'instanceofany' => static::instanceOfAny($value, is_array($expectation) ? $expectation : $args),
'includekeys', 'includekey',
'includeskey' => [(is_array($value) || is_object($value)) && static::allKeysExist($args, array: (array) $value), "include key", false],
'includevalues', 'includevalue',
'includesvalue' => [(is_array($value) || is_object($value)) && static::allValuesExist($args, array: (array) $value), "include value", false],
'implements', 'implementsinterface',
'implementinterface' => [
is_object($value) && in_array($expectation, class_implements($value), true),
"implements",
],
'contain', 'contains', 'containstring' => [
(
(is_array($value) && in_array($expectation, $value, true)) ||
(is_string($value) && strpos($value, $expectation) !== false)
),
"contain",
false,
],
'in' => [in_array($value, $args, true), "in"],
'equalwithdelta' => [abs($value - $expectation) <= $args[1], "", true, "equal to %s with delta %s"],
'closeto' => [is_numeric($value) && round($value, $args[1] ?? 2) === round($expectation, $args[1] ?? 2), "", true, "close to %s [precision: %s]"],
default => match (true) {
(
strpos($tag, $pre = 'has') === 0 ||
strpos($tag, $pre = 'have') === 0
) =>
static::hasHave(
$tag,
$pre,
$value,
$args,
),
default => [false, 'unknown'],
},
};
}
public static function __callStatic(
string $name,
array $args,
): void {
$tag = strtolower($name);
$invert = false;
if (
strpos($tag, $pre = 'not') === 0
|| strpos($tag, $pre = 'doesnt') === 0
) {
$invert = true;
$tag = substr($tag, strlen($pre));
}
static::assert(
$tag,
$value = $args[0],
$expectation = $args[1] ?? null,
$invert,
$name,
);
}
private static function assert(
string $tag,
mixed $value,
array $args,
bool $invert = false,
?string $name = null,
) {
$assertion = static::impl(
$tag,
$value,
$args,
);
[$condition, $message] = $assertion;
if ($message === 'unknown') {
throw new \BadMethodCallException("Unknown assertion:" . $name ?? $tag);
}
static::expect(
$value,
$invert,
$condition,
$message,
$args[1] ?? null,
$assertion[3] ?? true
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment