") !== false) return false; if ($strict) { if (class_exists($f)) return false; if (!function_exists($f)) return false; } return true; } /** * vérifier que $func est une fonction simple avec les règles de * {@link self::verifix_simple()} */ static function is_simple($func, bool $strict=true): bool { return self::verifix_simple($func, $strict); } private static function _is_invalid(?string $m): bool { return $m === null || $m === "" || $m === "::" || $m === "->"; } private static function _is_nfunction(?string $m): bool { return strpos($m, "\\") !== false; } private static function _parse_static(?string &$m): bool { $pos = strpos($m, "::"); if ($pos === false) return false; $m = substr($m, $pos + 2); return true; } private static function _parse_method(?string &$m): bool { $pos = strpos($m, "->"); if ($pos === false) return false; $m = substr($m, $pos + 2); return true; } private static function _parse_class_s(?string $cs, ?string &$c, ?string &$s): bool { if (self::_is_invalid($cs) || self::_parse_method($cs)) return false; $pos = strpos($cs, "::"); if ($pos === false) return false; if ($pos === 0) return false; $tmpc = substr($cs, 0, $pos); $cs = substr($cs, $pos + 2); if (self::_is_nfunction($cs)) return false; [$c, $s] = [$tmpc, cv::vn($cs)]; return true; } private static function _parse_c_static(?string $cs, ?string &$c, ?string &$s, ?bool &$bound): bool { if (self::_is_invalid($cs) || self::_parse_method($cs)) return false; $pos = strpos($cs, "::"); if ($pos === false) return false; if ($pos == strlen($cs) - 2) return false; if ($pos > 0) { $tmpc = substr($cs, 0, $pos); $bound = true; } else { $tmpc = null; $bound = false; } $cs = substr($cs, $pos + 2); if (self::_is_nfunction($cs)) return false; [$c, $s] = [$tmpc, cv::vn($cs)]; return true; } /** * vérifier que $func est une méthode statique, et la normaliser le cas * échéant. retourner true si c'est une méthode statique, false sinon * * les formes suivantes sont supportées (XXX étant null ou n'importe quelle * valeur scalaire de n'importe quel type sauf false) * - "XXX::function" * - ["XXX::function", ...$args] * - [XXX, "::function", ...$args] * - [XXX, "function", ...$args] c'est la forme normalisée * * Si XXX est une classe, la méthode statique est liée. sinon, elle doit être * liée à une classe avant d'être utilisée * * @param bool $strict vérifier l'existence de la classe et de la méthode si * la méthode est liée (ne pas uniquement faire une vérification syntaxique) */ static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null): bool { if (is_string($func)) { if (!self::_parse_c_static($func, $c, $f, $bound)) return false; $func = [$c, $f]; } elseif (is_array($func)) { if (!array_key_exists(0, $func)) return false; $c = $func[0]; if ($c === false) return false; if (self::_parse_class_s($c, $c, $f)) { $func[0] = $c; if ($f !== null) { # ["class::method"] --> ["class", "method"] array_splice($func, 1, 0, [$f]); } $bound = true; } elseif (self::_parse_c_static($c, $c, $f, $bound)) { # ["::method"] --> [null, "method"] array_splice($func, 0, 0, [null]); $func[1] = $f; } else { if (self::_is_invalid($c)) $c = null; $func[0] = $c; $bound = is_string($c); } # if (!array_key_exists(1, $func)) return false; $f = $func[1]; if (!is_string($f)) return false; if (self::_parse_c_static($f, $rc, $f, $rbound)) { if ($rc !== null && $c === null) { $c = $rc; $bound = $rbound; } } elseif (self::_is_invalid($f)) { return false; } elseif (self::_is_nfunction($f)) { return false; } elseif (self::_parse_method($f)) { return false; } elseif (self::_parse_static($f)) { } $func[1] = $f; } else { return false; } if ($strict && $bound && !method_exists($c, $f)) return false; return true; } /** * vérifier que $func est une méthode statique avec les règles de * {@link self::verifix_static()} */ static function is_static($func, bool $strict=true, ?bool &$bound=null): bool { return self::verifix_static($func, $strict, $bound); } private static function _parse_class_m(?string $cm, ?string &$c, ?string &$m): bool { if (self::_is_invalid($cm) || self::_parse_static($cm)) return false; $pos = strpos($cm, "->"); if ($pos === false) return false; if ($pos === 0) return false; $tmpc = substr($cm, 0, $pos); $cm = substr($cm, $pos + 2); if (self::_is_nfunction($cm)) return false; [$c, $m] = [$tmpc, cv::vn($cm)]; return true; } private static function _parse_c_method(?string $cm, ?string &$c, ?string &$m, ?bool &$bound): bool { if (self::_is_invalid($cm) || self::_parse_static($cm)) return false; $pos = strpos($cm, "->"); if ($pos === false) return false; if ($pos == strlen($cm) - 2) return false; if ($pos > 0) { $tmpc = substr($cm, 0, $pos); $bound = true; } else { $tmpc = null; $bound = false; } $cm = substr($cm, $pos + 2); if (self::_is_nfunction($cm)) return false; [$c, $m] = [$tmpc, cv::vn($cm)]; return true; } /** * vérifier que $func est une méthode non statique, et la normaliser le cas * échéant. retourner true si c'est une méthode non statique, false sinon * * les formes suivantes sont supportées (XXX étant null ou n'importe quelle * valeur scalaire de n'importe quel type sauf false) * - "XXX->function" * - ["XXX->function", ...$args] * - [XXX, "->function", ...$args] * - [XXX, "function", ...$args] c'est la forme normalisée * * Si XXX est une classe, la méthode est liée. sinon, elle doit être liée à un * objet avant d'être utilisée * * @param bool $strict vérifier l'existence de la classe et de la méthode si * la méthode est liée (ne pas uniquement faire une vérification syntaxique) */ static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null): bool { if (is_string($func)) { if (!self::_parse_c_method($func, $c, $f, $bound)) return false; $func = [$c, $f]; } elseif (is_array($func)) { if (!array_key_exists(0, $func)) return false; $c = $func[0]; if ($c === false) return false; if (self::_parse_class_m($c, $c, $f)) { $func[0] = $c; if ($f !== null) { # ["class->method"] --> ["class", "method"] array_splice($func, 1, 0, [$f]); } $bound = true; } elseif (self::_parse_c_method($c, $c, $f, $bound)) { # ["->method"] --> [null, "method"] array_splice($func, 0, 0, [null]); $func[1] = $f; } else { if (self::_is_invalid($c)) $c = null; $func[0] = $c; $bound = is_string($c); } # if (!array_key_exists(1, $func)) return false; $f = $func[1]; if (!is_string($f)) return false; if (self::_parse_c_method($f, $rc, $f, $rbound)) { if ($rc !== null && $c === null) { $c = $rc; $bound = $rbound; } } elseif (self::_is_invalid($f)) { return false; } elseif (self::_is_nfunction($f)) { return false; } elseif (self::_parse_static($f)) { return false; } elseif (self::_parse_method($f)) { } $func[1] = $f; } else { return false; } if ($strict && $bound && !method_exists($c, $f)) return false; return true; } /** * vérifier que $func est une méthode non statique avec les règles de * {@link self::verifix_method()} */ static function is_method($func, bool $strict=true, ?bool &$bound=null): bool { return self::verifix_method($func, $strict, $bound); } static function verifix_cons(&$func, bool $strict=true): bool { if (is_string($func)) { } elseif (is_array($func)) { } else { return false; } return false;#XXX } static function with($func, bool $strict=true): self { if ($func instanceof Closure) { return new self($func); } elseif (self::verifix_simple($func, $strict)) { return new self($func); } elseif (self::verifix_cons($func, $strict)) { return new self($func); } else { if (self::verifix_static($func, $strict, $bound)) { } elseif (self::verifix_method($func, $strict, $bound)) { } else { throw ValueException::invalid_type($func, "callable"); } if (!$bound) throw ValueException::invalid_value($func, null, "is not bound"); return new self($func); } } static function call($func, ...$args) { return self::with($func)->invoke($args); } static function with_object($func, object $object, bool $rebind=false, bool $strict=true): self { if ($func instanceof Closure) { return new self($func); } elseif (self::verifix_simple($func, $strict)) { return new self($func); } elseif (self::verifix_cons($func, $strict)) { return new self($func); } else { if (self::verifix_method($func, $strict, $bound)) { } elseif (self::verifix_static($func, $strict, $bound)) { } else { throw ValueException::invalid_type($func, "callable"); } if (!$bound || $rebind) $func[0] = $object; return new self($func); } } static function with_class($func, $class, bool $rebind=false, bool $strict=true): self { if ($func instanceof Closure) { return new self($func); } elseif (self::verifix_simple($func, $strict)) { return new self($func); } elseif (self::verifix_cons($func, $strict)) { return new self($func); } else { if (self::verifix_method($func, $strict, $bound)) { } elseif (self::verifix_static($func, $strict, $bound)) { } else { throw ValueException::invalid_type($func, "callable"); } if (!$bound || $rebind) { if (is_object($class)) $class = get_class($class); $func[0] = $class; } return new self($func); } } ############################################################################# const TYPE_CLOSURE = 0, TYPE_SIMPLE = 1, TYPE_STATIC = 2, TYPE_METHOD = 3, TYPE_CLASS = 4; function __construct($func) { $object = null; $prefixArgs = []; if ($func instanceof Closure) { $type = self::TYPE_CLOSURE; $reflection = new ReflectionFunction($func); $object = $func; } elseif ($func instanceof ReflectionFunction) { $type = self::TYPE_SIMPLE; $reflection = $func; } elseif ($func instanceof ReflectionMethod) { $type = self::TYPE_STATIC; $reflection = $func; } elseif ($func instanceof ReflectionClass) { $type = self::TYPE_CLASS; $reflection = $func; } elseif (is_array($func)) { if (count($func) > 2) { $prefixArgs = array_slice($func, 2); $func = array_slice($func, 0, 2); } [$c, $f] = $func; if ($c === false) { # fonction simple $type = self::TYPE_SIMPLE; $reflection = new ReflectionFunction($f); } elseif ($f === false) { # classe $type = self::TYPE_CLASS; $reflection = new ReflectionClass($c); } elseif ($c !== null) { # methode $reflection = new ReflectionMethod($func); if (is_object($c)) { $type = self::TYPE_METHOD; $object = $c; } else { $type = self::TYPE_STATIC; } } else { throw new StateException("invalid func"); } } else { throw new StateException("invalid func"); } if ($reflection instanceof ReflectionClass) { $constructor = $reflection->getConstructor(); if ($constructor === null) { $variadic = false; $minArgs = $maxArgs = 0; } else { $variadic = $constructor->isVariadic(); $minArgs = $constructor->getNumberOfRequiredParameters(); $maxArgs = $constructor->getNumberOfParameters(); } } else { $variadic = $reflection->isVariadic(); $minArgs = $reflection->getNumberOfRequiredParameters(); $maxArgs = $reflection->getNumberOfParameters(); } $this->type = $type; $this->reflection = $reflection; $this->variadic = $variadic; $this->minArgs = $minArgs; $this->maxArgs = $maxArgs; $this->object = $object; $this->prefixArgs = $prefixArgs; } protected int $type; /** @var ReflectionFunction|ReflectionMethod|ReflectionClass */ protected $reflection; protected bool $variadic; protected int $minArgs; protected int $maxArgs; protected ?object $object; protected array $prefixArgs; function invoke(?array $args=null) { $args = array_merge($this->prefixArgs, $args ?? []); if (!$this->variadic) $args = array_slice($args, 0, $this->maxArgs); $minArgs = $this->minArgs; while (count($args) < $minArgs) $args[] = null; switch ($this->type) { case self::TYPE_CLOSURE: /** @var Closure $closure */ $closure = $this->object; return $closure(...$args); case self::TYPE_SIMPLE: case self::TYPE_STATIC: /** @var ReflectionFunction $function */ $function = $this->reflection; return $function->invoke(...$args); case self::TYPE_METHOD: /** @var ReflectionMethod $method */ $method = $this->reflection; return $method->invoke($this->object, ...$args); case self::TYPE_CLASS: /** @var ReflectionClass $class */ $class = $this->reflection; return $class->newInstance(...$args); } } }