Created
November 2, 2024 00:13
-
-
Save oplanre/f54e3bf71f90844c00675b835e4cb4ef to your computer and use it in GitHub Desktop.
Assertion class PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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