Skip to content

Instantly share code, notes, and snippets.

@ircmaxell
Last active March 1, 2023 23:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ircmaxell/10067861 to your computer and use it in GitHub Desktop.
Save ircmaxell/10067861 to your computer and use it in GitHub Desktop.
Zend Parse Parameters. In PHP
<?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