22declare(strict_types=1);
36use Ramsey\Uuid\UuidInterface;
37use
function array_combine;
38use
function array_map;
39use
function array_reverse;
40use
function array_values;
42use
function chunk_split;
43use
function class_exists;
45use
function debug_zval_dump;
50use
function file_exists;
51use
function file_get_contents;
52use
function function_exists;
53use
function get_class;
54use
function get_current_user;
55use
function get_loaded_extensions;
59use
function interface_exists;
64use
function is_infinite;
67use
function is_object;
68use
function is_string;
69use
function mb_check_encoding;
70use
function ob_end_clean;
71use
function ob_get_contents;
73use
function opcache_get_status;
75use
function php_uname;
76use
function phpversion;
77use
function preg_grep;
78use
function preg_match;
79use
function preg_match_all;
80use
function preg_replace;
81use
function shell_exec;
82use
function spl_object_id;
83use
function str_ends_with;
85use
function str_split;
86use
function str_starts_with;
90use
function sys_get_temp_dir;
92use
function xdebug_get_function_stack;
95use
const PHP_INT_SIZE;
96use
const PHP_MAXPATHLEN;
97use
const STR_PAD_LEFT;
98use
const STR_PAD_RIGHT;
104 public const OS_WINDOWS =
"win";
105 public const OS_IOS =
"ios";
106 public const OS_MACOS =
"mac";
107 public const OS_ANDROID =
"android";
108 public const OS_LINUX =
"linux";
109 public const OS_BSD =
"bsd";
110 public const OS_UNKNOWN =
"other";
112 private static ?
string $os =
null;
113 private static ?UuidInterface $serverUniqueId =
null;
114 private static ?
int $cpuCores =
null;
123 $func = new \ReflectionFunction($closure);
124 if(!str_ends_with($func->getName(),
'{closure}')){
128 $scope = $func->getClosureScopeClass();
132 ($func->getClosureThis() !==
null ?
"->" :
"::") .
137 return $func->getName();
139 $filename = $func->getFileName();
141 return "closure@" . ($filename !==
false ?
142 Filesystem::cleanPath($filename) .
"#L" . $func->getStartLine() :
153 $reflect = new \ReflectionClass($obj);
154 if($reflect->isAnonymous()){
155 $filename = $reflect->getFileName();
157 return "anonymous@" . ($filename !==
false ?
158 Filesystem::cleanPath($filename) .
"#L" . $reflect->getStartLine() :
163 return $reflect->getName();
170 return static function(object $o){
187 $callback = self::cloneCallback();
188 return array_map($callback, $array);
200 if(self::$serverUniqueId !== null && $extra ===
""){
201 return self::$serverUniqueId;
204 $machine = php_uname(
"a");
205 $cpuinfo = @file(
"/proc/cpuinfo");
206 if($cpuinfo !==
false){
207 $cpuinfoLines = preg_grep(
"/(model name|Processor|Serial)/", $cpuinfo);
208 if($cpuinfoLines ===
false){
211 $machine .= implode(
"", $cpuinfoLines);
213 $machine .= sys_get_temp_dir();
215 $os = Utils::getOS();
216 if($os === Utils::OS_WINDOWS){
217 @exec(
"ipconfig /ALL", $mac);
218 $mac = implode(
"\n", $mac);
219 if(preg_match_all(
"#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches) > 0){
220 foreach($matches[1] as $i => $v){
221 if($v ==
"00-00-00-00-00-00"){
222 unset($matches[1][$i]);
225 $machine .= implode(
" ", $matches[1]);
227 }elseif($os === Utils::OS_LINUX){
228 if(file_exists(
"/etc/machine-id")){
229 $machine .= file_get_contents(
"/etc/machine-id");
231 @exec(
"ifconfig 2>/dev/null", $mac);
232 $mac = implode(
"\n", $mac);
233 if(preg_match_all(
"#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches) > 0){
234 foreach($matches[1] as $i => $v){
235 if($v ==
"00:00:00:00:00:00"){
236 unset($matches[1][$i]);
239 $machine .= implode(
" ", $matches[1]);
242 }elseif($os === Utils::OS_ANDROID){
243 $machine .= @file_get_contents(
"/system/build.prop");
244 }elseif($os === Utils::OS_MACOS){
245 $machine .= shell_exec(
"system_profiler SPHardwareDataType | grep UUID");
247 $data = $machine . PHP_MAXPATHLEN;
248 $data .= PHP_INT_MAX;
249 $data .= PHP_INT_SIZE;
250 $data .= get_current_user();
251 foreach(get_loaded_extensions() as $ext){
252 $data .= $ext .
":" . phpversion($ext);
256 $uuid = Uuid::uuid3(Uuid::NIL, $data);
259 self::$serverUniqueId = $uuid;
275 public static function getOS(
bool $recalculate =
false) : string{
276 if(self::$os === null || $recalculate){
277 $uname = php_uname(
"s");
278 if(stripos($uname,
"Darwin") !==
false){
279 if(str_starts_with(php_uname(
"m"),
"iP")){
280 self::$os = self::OS_IOS;
282 self::$os = self::OS_MACOS;
284 }elseif(stripos($uname,
"Win") !==
false || $uname ===
"Msys"){
285 self::$os = self::OS_WINDOWS;
286 }elseif(stripos($uname,
"Linux") !==
false){
287 if(@file_exists(
"/system/build.prop")){
288 self::$os = self::OS_ANDROID;
290 self::$os = self::OS_LINUX;
292 }elseif(stripos($uname,
"BSD") !==
false || $uname ===
"DragonFly"){
293 self::$os = self::OS_BSD;
295 self::$os = self::OS_UNKNOWN;
302 public static function getCoreCount(
bool $recalculate =
false) : int{
303 if(self::$cpuCores !== null && !$recalculate){
304 return self::$cpuCores;
308 switch(Utils::getOS()){
309 case Utils::OS_LINUX:
310 case Utils::OS_ANDROID:
311 if(($cpuinfo = @file(
'/proc/cpuinfo')) !==
false){
312 foreach($cpuinfo as $l){
313 if(preg_match(
'/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){
317 }elseif(($cpuPresent = @file_get_contents(
"/sys/devices/system/cpu/present")) !==
false){
318 if(preg_match(
"/^([0-9]+)\\-([0-9]+)$/", trim($cpuPresent), $matches) > 0){
319 $processors = ((int) $matches[2]) - ((int) $matches[1]);
324 case Utils::OS_MACOS:
325 $processors = (int) shell_exec(
"sysctl -n hw.ncpu");
327 case Utils::OS_WINDOWS:
328 $processors = (int) getenv(
"NUMBER_OF_PROCESSORS");
331 return self::$cpuCores = $processors;
337 public static function hexdump(
string $bin) : string{
339 $bin = str_split($bin, 16);
340 foreach($bin as $counter => $line){
341 $hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32,
" ", STR_PAD_RIGHT), 2,
" "), 24,
" ");
342 $ascii = preg_replace(
'#([^\x20-\x7E])#',
".", $line);
343 $output .= str_pad(dechex($counter << 4), 4,
"0", STR_PAD_LEFT) .
" " . $hex .
" " . $ascii . PHP_EOL;
353 if(!is_string($str)){
354 return gettype($str);
357 return preg_replace(
'#([^\x20-\x7E])#',
'.', $str);
360 public static function javaStringHash(
string $string) : int{
362 for($i = 0, $len = strlen($string); $i < $len; $i++){
363 $ord = ord($string[$i]);
364 if(($ord & 0x80) !== 0){
367 $hash = 31 * $hash + $ord;
373 public static function getReferenceCount(
object $value,
bool $includeCurrent =
true) : int{
375 debug_zval_dump($value);
376 $contents = ob_get_contents();
377 if($contents ===
false)
throw new AssumptionFailedError(
"ob_get_contents() should never return false here");
378 $ret = explode(
"\n", $contents);
381 if(preg_match(
'/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
382 return ((
int) $m[1]) - ($includeCurrent ? 3 : 4);
387 private static function printableExceptionMessage(\Throwable $e) : string{
388 $errstr = preg_replace(
'/\s+/',
' ', trim($e->getMessage()));
390 $errno = $e->getCode();
393 $errno = ErrorTypeToStringMap::get($errno);
394 }
catch(\InvalidArgumentException $ex){
399 $errfile = Filesystem::cleanPath($e->getFile());
400 $errline = $e->getLine();
402 return get_class($e) .
": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
411 $trace = $e->getTrace();
414 $lines = [self::printableExceptionMessage($e)];
415 $lines[] =
"--- Stack trace ---";
416 foreach(Utils::printableTrace($trace) as $line){
417 $lines[] =
" " . $line;
419 for($prev = $e->getPrevious(); $prev !==
null; $prev = $prev->getPrevious()){
420 $lines[] =
"--- Previous ---";
421 $lines[] = self::printableExceptionMessage($prev);
422 foreach(Utils::printableTrace($prev->getTrace()) as $line){
423 $lines[] =
" " . $line;
426 $lines[] =
"--- End of exception information ---";
430 private static function stringifyValueForTrace(mixed $value,
int $maxStringLength) : string{
432 is_object($value) =>
"object " . self::getNiceClassName($value) .
"#" . spl_object_id($value),
433 is_array($value) =>
"array[" . count($value) .
"]",
434 is_string($value) =>
"string[" . strlen($value) .
"] " . substr(Utils::printable($value), 0, $maxStringLength),
435 is_bool($value) => $value ?
"true" :
"false",
436 is_int($value) =>
"int " . $value,
437 is_float($value) =>
"float " . $value,
438 $value ===
null =>
"null",
439 default => gettype($value) .
" " . Utils::printable((
string) $value)
449 public static function printableTrace(array $trace,
int $maxStringLength = 80) : array{
451 for($i = 0; isset($trace[$i]); ++$i){
453 if(isset($trace[$i][
"args"]) || isset($trace[$i][
"params"])){
454 if(isset($trace[$i][
"args"])){
455 $args = $trace[$i][
"args"];
457 $args = $trace[$i][
"params"];
463 foreach($args as $argId => $value){
464 $paramsList[] = ($argId === $offset ?
"" :
"$argId: ") . self::stringifyValueForTrace($value, $maxStringLength);
467 $params = implode(
", ", $paramsList);
469 $messages[] =
"#$i " . (isset($trace[$i][
"file"]) ? Filesystem::cleanPath($trace[$i][
"file"]) :
"") .
"(" . (isset($trace[$i][
"line"]) ? $trace[$i][
"line"] :
"") .
"): " . (isset($trace[$i][
"class"]) ? $trace[$i][
"class"] . (($trace[$i][
"type"] ===
"dynamic" || $trace[$i][
"type"] ===
"->") ?
"->" :
"::") :
"") . $trace[$i][
"function"] .
"(" . Utils::printable($params) .
")";
484 $printableTrace = self::printableTrace($rawTrace, $maxStringLength);
486 foreach($printableTrace as $frameId => $printableFrame){
487 $rawFrame = $rawTrace[$frameId];
490 $rawFrame[
"file"] ??
"unknown",
491 $rawFrame[
"line"] ?? 0
504 if(function_exists(
"xdebug_get_function_stack") && count($trace = @xdebug_get_function_stack()) !== 0){
505 $trace = array_reverse($trace);
507 $e = new \Exception();
508 $trace = $e->getTrace();
510 for($i = 0; $i < $skipFrames; ++$i){
513 return array_values($trace);
520 return self::printableTrace(self::currentTrace(++$skipFrames));
529 $rawDocComment = substr($docComment, 3, -2);
530 preg_match_all(
'/(*ANYCRLF)^[\t ]*(?:\* )?@([a-zA-Z\-]+)(?:[\t ]+(.+?))?[\t ]*$/m', $rawDocComment, $matches);
532 return array_combine($matches[1], $matches[2]);
540 $baseInterface = false;
541 if(!class_exists($baseName)){
542 if(!interface_exists($baseName)){
543 throw new \InvalidArgumentException(
"Base class $baseName does not exist");
545 $baseInterface =
true;
547 if(!class_exists($className)){
548 throw new \InvalidArgumentException(
"Class $className does not exist or is not a class");
550 if(!is_a($className, $baseName,
true)){
551 throw new \InvalidArgumentException(
"Class $className does not " . ($baseInterface ?
"implement" :
"extend") .
" $baseName");
553 $class = new \ReflectionClass($className);
554 if(!$class->isInstantiable()){
555 throw new \InvalidArgumentException(
"Class $className cannot be constructed");
573 $signature = CallbackType::createFromCallable($signature);
575 if(!$signature->isSatisfiedBy($subject)){
576 throw new \TypeError(
"Declaration of callable `" . CallbackType::createFromCallable($subject) .
"` must be compatible with `" . $signature .
"`");
586 foreach($array as $k => $v){
589 }
catch(\TypeError $e){
590 throw new \TypeError(
"Incorrect type of element at \"$k\": " . $e->getMessage(), 0, $e);
606 foreach($array as $key => $value){
607 yield (
string) $key => $value;
611 public static function checkUTF8(
string $string) : void{
612 if(!mb_check_encoding($string,
'UTF-8')){
613 throw new \InvalidArgumentException(
"Text must be valid UTF-8");
623 public static function assumeNotFalse(mixed $value, \Closure|
string $context =
"This should never be false") : mixed{
624 if($value === false){
625 throw new AssumptionFailedError(
"Assumption failure: " . (is_string($context) ? $context : $context()) .
" (THIS IS A BUG)");
630 public static function checkFloatNotInfOrNaN(
string $name,
float $float) : void{
632 throw new \InvalidArgumentException(
"$name cannot be NaN");
634 if(is_infinite($float)){
635 throw new \InvalidArgumentException(
"$name cannot be infinite");
639 public static function checkVector3NotInfOrNaN(Vector3 $vector3) : void{
640 if($vector3 instanceof Location){
641 self::checkFloatNotInfOrNaN(
"yaw", $vector3->yaw);
642 self::checkFloatNotInfOrNaN(
"pitch", $vector3->pitch);
644 self::checkFloatNotInfOrNaN(
"x", $vector3->x);
645 self::checkFloatNotInfOrNaN(
"y", $vector3->y);
646 self::checkFloatNotInfOrNaN(
"z", $vector3->z);
649 public static function checkLocationNotInfOrNaN(Location $location) : void{
650 self::checkVector3NotInfOrNaN($location);
659 function_exists(
'opcache_get_status') &&
660 ($opcacheStatus = opcache_get_status(false)) !== false &&
661 isset($opcacheStatus[
"jit"][
"on"])
663 $jit = $opcacheStatus[
"jit"];
664 if($jit[
"on"] ===
true){
665 return (($jit[
"opt_flags"] >> 2) * 1000) +
666 (($jit[
"opt_flags"] & 0x03) * 100) +
667 ($jit[
"kind"] * 10) +
static printableExceptionInfo(\Throwable $e, $trace=null)
static parseDocComment(string $docComment)
static assumeNotFalse(mixed $value, \Closure|string $context="This should never be false")
static validateArrayValueType(array $array, \Closure $validator)
static validateCallableSignature(callable|CallbackType $signature, callable $subject)
static getMachineUniqueId(string $extra="")
static stringifyKeys(array $array)
static hexdump(string $bin)
static getNiceClosureName(\Closure $closure)
static getOS(bool $recalculate=false)
static currentTrace(int $skipFrames=0)
static printable(mixed $str)
static printableTraceWithMetadata(array $rawTrace, int $maxStringLength=80)
static testValidInstance(string $className, string $baseName)
static getOpcacheJitMode()
static getNiceClassName(object $obj)
static cloneObjectArray(array $array)
static printableTrace(array $trace, int $maxStringLength=80)
static printableCurrentTrace(int $skipFrames=0)