Last active
March 1, 2023 23:48
-
-
Save ircmaxell/10067861 to your computer and use it in GitHub Desktop.
Zend Parse Parameters. In PHP
This file contains 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 | |
namespace ZPP; | |
const IS_STRING = "string"; | |
const IS_BOOL = "boolean"; | |
const IS_LONG = "integer"; | |
const IS_DOUBLE = "double"; | |
const IS_ARRAY = "array"; | |
const IS_OBJECT = "object"; | |
const IS_RESOURCE = "resource"; | |
const IS_NULL = "NULL"; | |
function zend_parse_parameters($type_spec, array $results) { | |
$bt = debug_backtrace(0, 2); | |
if (isset($bt[1]['args']) && is_array($bt[1]['args'])) { | |
$arguments = $bt[1]['args']; | |
} else { | |
throw new \BadFunctionCallException("You must call ZPP from a function context, not globally"); | |
} | |
if (strlen($type_spec) == 0 && !empty($arguments)) { | |
generate_error("LogicException", "expects 0 parameters, %d given", count($arguments)); | |
} elseif (isset($type_spec[0]) && $type_spec[0] == '*' || $type_spec[0] == '+') { | |
// handle case where var-args are only speced: | |
$type_spec = 'z' . $type_spec; | |
} | |
$spec_len = strlen($type_spec); | |
$num_args = count($arguments); | |
$have_varargs = false; | |
$max_num_args = 0; | |
$min_num_args = -1; | |
$post_varargs = -1; | |
for ($i = 0; $i < $spec_len; $i++) { | |
$c = $type_spec[$i]; | |
switch ($c) { | |
case 'l': case 'd': | |
case 's': case 'b': | |
case 'r': case 'a': | |
case 'o': case 'O': | |
case 'z': case 'Z': | |
case 'C': case 'h': | |
case 'f': case 'A': | |
case 'H': case 'p': | |
$max_num_args++; | |
break; | |
case '|': | |
$min_num_args = $max_num_args; | |
break; | |
case '/': | |
case '!': | |
break; | |
case '*': | |
$max_num_args--; | |
// Fall-through intentional | |
case '+': | |
if ($have_varargs) { | |
generate_error("LogicException", "only one varargs specifier (* or +) is permitted"); | |
} | |
$have_varargs = true; | |
$post_varargs = $max_num_args; | |
break; | |
default: | |
generate_error("LogicException", "bad type specifier while parsing parameters"); | |
} | |
} | |
if ($min_num_args < 0) { | |
$min_num_args = $max_num_args; | |
} | |
if ($have_varargs) { | |
$post_varargs = $max_num_args - $post_varargs; | |
$max_num_args = -1; | |
} | |
if ($num_args < $min_num_args || ($num_args > $max_num_args && $max_num_args > 0)) { | |
generate_error( | |
"RuntimeException", | |
"expects %s %d parameter%s, %d given", | |
$min_num_args == $max_num_args ? "exactly" : ($num_args < $min_num_args ? "at least" : "at most"), | |
$num_args < $min_num_args ? $min_num_args : $max_num_args, | |
($num_args < $min_num_args ? $min_num_args : $max_num_args) == 1 ? "" : "s", | |
$num_args | |
); | |
} | |
$type_key = 0; | |
$result_num = 0; | |
for ($i = 0; $i < $num_args; $i++, $result_num++) { | |
if ($type_spec[$type_key] == '|') { | |
$type_key++; | |
} | |
if ($type_key + 1 < $spec_len && ($type_spec[$type_key + 1] == '*' || $type_spec[$type_key + 1] == '+')) { | |
$num_varargs = $num_args - $i - $post_varargs; | |
if ($num_varargs > 0) { | |
$var_arg_result = []; | |
$start_type_key = $type_key; | |
$end_type_key = $type_key; | |
while ($num_varargs > 0) { | |
if ($type_spec[$type_key] == "C") { | |
$class = (string) $results[$result_num]; | |
} | |
parse_arg($i, $result_num, $arguments, $type_spec, $type_key, $results); | |
$var_arg_result[] = $results[$result_num]; | |
$i++; | |
$end_type_key = $type_key; | |
$type_key = $start_type_key; | |
$num_varargs--; | |
if ($type_spec[$type_key] == 'C') { | |
$results[$result_num] = $class; | |
} elseif ($type_spec[$type_key] == 'O') { | |
//extra is stored | |
$result_num--; | |
} | |
} | |
if ($type_spec[$type_key] == 'O') { | |
//extra is stored | |
$result_num++; | |
} | |
$results[$result_num] = $var_arg_result; | |
// subtract 1 from the arg position, to count for the duplicate shift: | |
$i--; | |
$type_key = $end_type_key + 1; | |
continue; | |
} else { | |
$type_key++; | |
} | |
} | |
parse_arg($i, $result_num, $arguments, $type_spec, $type_key, $results); | |
} | |
} | |
function parse_arg($arg_num, &$result_num, array $arguments, $type_spec, &$type_key, array &$results) { | |
$severity = E_USER_WARNING; | |
$error = null; | |
$type = parse_arg_impl($arg_num, $result_num, $arguments, $type_spec, $type_key, $results, $severity, $error); | |
if ($type) { | |
if ($error) { | |
generate_error("RuntimeException", "expects parameter %d %s", $arg_num, $error); | |
} else { | |
generate_error("RuntimeException", "expects parameter %d to be %s, %s given", $arg_num, $type, gettype($arguments[$arg_num])); | |
} | |
} | |
} | |
function parse_arg_impl($arg_num, &$result_num, array $arguments, $type_spec, &$type_key, array &$results, &$severity, &$error) { | |
$by_ref = false; | |
$walk = $type_key + 1; | |
$nullable = false; | |
while (true) { | |
if ($type_spec[$walk] == '/') { | |
// Todo: Implement this | |
} elseif ($type_spec[$walk] == "!") { | |
$nullable = true; | |
} else { | |
break; | |
} | |
$walk++; | |
} | |
$c = $type_spec[$type_key]; | |
$arg = $arguments[$arg_num]; | |
switch ($c) { | |
case 'l': | |
case 'L': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} | |
switch (gettype($arg)) { | |
case IS_STRING: | |
if (!is_numeric($arg)) { | |
return "long"; | |
} | |
$arg = (double) $arg; | |
case IS_DOUBLE: | |
if ($c == 'L') { | |
if ($arg > PHP_INT_MAX) { | |
$arg = PHP_INT_MAX; | |
} elseif ($arg < ~PHP_INT_MAX) { | |
$arg = ~PHP_INT_MAX; | |
} | |
} | |
$results[$result_num] = (int) $arg; | |
break; | |
case IS_NULL: | |
case IS_LONG: | |
case IS_BOOL: | |
$results[$result_num] = (int) $arg; | |
break; | |
default: | |
return "long"; | |
} | |
break; | |
case 'd': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} | |
switch (gettype($arg)) { | |
case IS_STRING: | |
if (!is_numeric($arg)) { | |
return "double"; | |
} | |
case IS_NULL: | |
case IS_LONG: | |
case IS_DOUBLE: | |
case IS_BOOL: | |
$results[$result_num] = (double) $arg; | |
break; | |
default: | |
return "double"; | |
} | |
break; | |
case 'p': | |
case 's': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} | |
switch (gettype($arg)) { | |
case IS_OBJECT: | |
if (!is_callable([$arg, "__toString"])) { | |
return $c == "s" ? "string" : "a valid path"; | |
} | |
// fall through intentional | |
case IS_NULL: | |
case IS_STRING: | |
case IS_LONG: | |
case IS_DOUBLE: | |
case IS_BOOL: | |
$arg = (string) $arg; | |
if ($c == 'p' && strpos($arg, "\0") !== false) { | |
return "a valid path"; | |
} | |
$results[$result_num] = $arg; | |
break; | |
default: | |
return $c == "s" ? "string" : "a valid path"; | |
} | |
break; | |
case 'b': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} | |
switch (gettype($arg)) { | |
case IS_NULL: | |
case IS_STRING: | |
case IS_LONG: | |
case IS_DOUBLE: | |
case IS_BOOL: | |
$results[$result_num] = (bool) $arg; | |
break; | |
default: | |
return "boolean"; | |
} | |
break; | |
case 'r': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) != IS_RESOURCE) { | |
return "resource"; | |
} | |
$results[$result_num] = $arg; | |
break; | |
case 'a': | |
case 'A': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) == IS_ARRAY || ($c == "A" && gettype($arg) == IS_OBJECT)) { | |
$results[$result_num] = $arg; | |
} else { | |
return "array"; | |
} | |
break; | |
case 'H': | |
case 'h': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) == IS_ARRAY) { | |
$results[$result_num] = $arg; | |
} elseif ($c == 'H' && gettype($arg) == IS_OBJECT && $arg instanceof \ArrayAccess) { | |
$results[$result_num] = $arg; | |
} else { | |
return "array"; | |
} | |
break; | |
case 'i': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) == IS_ARRAY) { | |
$results[$result_num] = new \ArrayIterator($arg); | |
} elseif (gettype($arg) == IS_OBJECT) { | |
if ($arg instanceof \Iterator) { | |
$results[$result_num] = $arg | |
} elseif ($arg instanceof \IteratorAggregate) { | |
$results[$result_num] = new \IteratorIterator($arg); | |
} else { | |
return 'iterable'; | |
} | |
} else { | |
return 'iterable'; | |
} | |
case 'o': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) == IS_OBJECT) { | |
$results[$result_num] = $arg; | |
} else { | |
return "object"; | |
} | |
break; | |
case 'O': | |
$class = $results[$result_num++]; | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (gettype($arg) == IS_OBJECT) { | |
if ($arg instanceof $class) { | |
$results[$result_num] = $arg; | |
} else { | |
return is_object($class) ? get_class($class) : (string) $class; | |
} | |
} else { | |
return is_object($class) ? get_class($class) : (string) $class; | |
} | |
break; | |
case 'C': | |
$class = $results[$result_num]; | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} | |
$arg = (string) $arg; | |
if (!class_exists($arg)) { | |
$error = sprintf("to be a valid class name, '%s' given", $arg); | |
return true; | |
} elseif ($class && !extends_from($arg, is_object($class) ? get_class($class) : (string) $class)) { | |
$error = sprintf("to be a class name derived from %s, '%s' given", is_object($class) ? get_class($class) : (string) $class, $arg); | |
return true; | |
} | |
$results[$result_num] = $arg; | |
break; | |
case 'f': | |
if ($nullable && is_null($arg)) { | |
$results[$result_num] = null; | |
break; | |
} elseif (!is_callable($arg)) { | |
return "valid callback"; | |
} | |
$results[$result_num] = $arg; | |
break; | |
case 'z': | |
case "Z": | |
$results[$result_num] = $arguments[$arg_num]; | |
break; | |
default: | |
return "unknown"; | |
} | |
$type_key = $walk; | |
} | |
function extends_from($class, $parent) { | |
$lowerparent = strtolower($parent); | |
if ($lowerparent == strtolower($class)) { | |
return true; | |
} | |
foreach (array_merge(class_implements($class), class_parents($class)) as $test) { | |
if ($lowerparent == strtolower($test)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function generate_error($class, $message) { | |
$bt = debug_backtrace(); | |
$callerIsNext = false; | |
$caller = PHP_INT_MAX; | |
for ($stackCount = 0; $stackCount < count($bt); $stackCount++) { | |
if ($callerIsNext) { | |
$caller = $stackCount; | |
break; | |
} elseif ($bt[$stackCount]["file"] != __FILE__) { | |
$callerIsNext = true; | |
} | |
} | |
if (!isset($bt[$caller])) { | |
// We have a stack error, bail | |
throw new \RuntimeException("Invalid stack, something went wrong here"); | |
} | |
if (func_num_args() > 2) { | |
$message = vsprintf($message, array_slice(func_get_args(), 2)); | |
} | |
$tmp = sprintf( | |
"%s%s%s(): %s in %s on line %d", | |
$bt[$caller]['class'], | |
$bt[$caller]['type'], | |
$bt[$caller]['function'], | |
$message, | |
$bt[$caller]['file'], | |
$bt[$caller]['line'] | |
); | |
throw new $class($tmp); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment