"; } private static function _is_nfunction(?string $f): bool { return strpos($f, "\\") !== 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; } ############################################################################# # Fonctions /** * vérifier que $func est une fonction simple et la normaliser le cas échéant. * retourner true si c'est une fonction simple, false sinon * * les formes suivantes sont supportées: * - "function" si une classe du même nom n'existe pas déjà * - [false, "function", ...$args] c'est la forme normalisée * * @param bool $strict vérifier l'inexistence de la classe et l'existence de * la fonction (ne pas uniquement faire une vérification syntaxique) */ static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool { if ($strict) { $msg = var_export($func, true); $reason = null; } if ($func instanceof ReflectionFunction) return true; if (is_string($func)) { $c = false; $f = $func; } elseif (is_array($func)) { if (!array_key_exists(0, $func)) return false; $c = $func[0]; if (!array_key_exists(1, $func)) return false; $f = $func[1]; } else { return false; } if ($c !== false) return false; if (!is_string($f)) return false; if (self::_is_invalid($f)) return false; if (self::_parse_static($f)) return false; if (self::_parse_method($f)) return false; if ($strict) { $reason = null; if (class_exists($f)) { $reason = "$msg: is a class"; return false; } if (!function_exists($f)) { $reason = "$msg: function not found"; return false; } } $func = [false, $f]; return true; } /** * vérifier que $func est une fonction simple avec les règles de * {@link self::verifix_function()} */ static function is_function($func, bool $strict=true, ?string &$reason=null): bool { return self::verifix_function($func, $strict, $reason); } ############################################################################# # Classes /** * vérifier que $func est une classe et la normaliser le cas échéant. * retourner true si c'est une classe, false sinon * * les formes suivantes sont supportées: * - "class" * - ["class", false, ...$args] c'est la forme normalisée * * @param bool $strict vérifier l'existence de la classe (ne pas uniquement * faire une vérification syntaxique) */ static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool { if ($strict) { $msg = var_export($func, true); $reason = null; } if ($func instanceof ReflectionClass) return true; if (is_string($func)) { $c = $func; $f = false; } elseif (is_array($func)) { if (!array_key_exists(0, $func)) return false; $c = $func[0]; if (!array_key_exists(1, $func)) return false; $f = $func[1]; } else { return false; } if (!is_string($c)) return false; if (self::_is_invalid($c)) return false; if (self::_parse_static($c)) return false; if (self::_parse_method($c)) return false; if ($f !== false) return false; if ($strict) { if (!class_exists($c)) { $reason = "$msg: class not found"; return false; } } $func = [$c, false]; return true; } /** * vérifier que $func est une classe avec les règles de * {@link self::verifix_class()} */ static function is_class($func, bool $strict=true, ?string &$reason=null): bool { return self::verifix_class($func, $strict, $reason); } ############################################################################# # Méthodes statiques 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, ?string &$reason=null): bool { if ($strict) { $msg = var_export($func, true); $reason = null; } if ($func instanceof ReflectionMethod) { $bound = false; return true; } if (is_string($func)) { if (!self::_parse_c_static($func, $c, $f, $bound)) return false; $cf = [$c, $f]; } elseif (is_array($func)) { $cf = $func; if (!array_key_exists(0, $cf)) return false; $c = $cf[0]; if ($c === false) return false; if (is_object($c)) $c = get_class($c); if (is_string($c)) { if (self::_is_invalid($c)) return false; if (self::_parse_class_s($c, $c, $f)) { $cf[0] = $c; if ($f !== null) { # ["class::method"] --> ["class", "method"] array_splice($cf, 1, 0, [$f]); } $bound = true; } elseif (self::_parse_c_static($c, $c, $f, $bound)) { # ["::method"] --> [null, "method"] array_splice($cf, 0, 0, [null]); $cf[1] = $f; } else { $cf[0] = $c; $bound = is_string($c); } } else { $cf[0] = null; $bound = false; } # if (!array_key_exists(1, $cf)) return false; $f = $cf[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; } } else { if (self::_is_invalid($f)) return false; if (self::_is_nfunction($f)) return false; if (self::_parse_method($f)) return false; self::_parse_static($f); } $cf[1] = $f; } else { return false; } if ($strict) { $reason = null; if ($bound) { if (!class_exists($c)) { $reason = "$msg: class not found"; return false; } if (!method_exists($c, $f)) { $reason = "$msg: method not found"; return false; } } else { $reason = "$msg: not bound"; } } $func = $cf; 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, ?string &$reason=null): bool { return self::verifix_static($func, $strict, $bound, $reason); } ############################################################################# # Méthodes non statiques 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, ?string &$reason=null): bool { if ($strict) { $msg = var_export($func, true); $reason = null; } if ($func instanceof ReflectionMethod) { $bound = false; return true; } if (is_string($func)) { if (!self::_parse_c_method($func, $c, $f, $bound)) return false; $cf = [$c, $f]; } elseif (is_array($func)) { $cf = $func; if (!array_key_exists(0, $cf)) return false; $c = $cf[0]; if ($c === false) return false; if (is_object($c)) { $bound = true; } elseif (is_string($c)) { if (self::_is_invalid($c)) return false; if (self::_parse_class_m($c, $c, $f)) { $cf[0] = $c; if ($f !== null) { # ["class->method"] --> ["class", "method"] array_splice($cf, 1, 0, [$f]); } $bound = true; } elseif (self::_parse_c_method($c, $c, $f, $bound)) { # ["->method"] --> [null, "method"] array_splice($cf, 0, 0, [null]); $cf[1] = $f; } else { $cf[0] = $c; $bound = is_string($c); } } else { $cf[0] = null; $bound = false; } # if (!array_key_exists(1, $cf)) return false; $f = $cf[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; } } else { if (self::_is_invalid($f)) return false; if (self::_is_nfunction($f)) return false; if (self::_parse_static($f)) return false; self::_parse_method($f); } $cf[1] = $f; } else { return false; } if ($strict) { $reason = null; if ($bound) { if (!is_object($c) && !class_exists($c)) { $reason = "$msg: class not found"; return false; } if (!method_exists($c, $f)) { $reason = "$msg: method not found"; return false; } } else { $reason = "$msg: not bound"; } } $func = $cf; 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, ?string &$reason=null): bool { return self::verifix_method($func, $strict, $bound, $reason); } ############################################################################# # func static function with($func, bool $strict=true): self { if ($func instanceof Closure) { return new self($func, false, null); } elseif (self::verifix_function($func, $strict, $reason)) { return new self($func, false, $reason); } elseif (self::verifix_class($func, $strict, $reason)) { return new self($func, false, $reason); } else { $valid = true; if (is_array($func) && array_key_exists(0, $func) && is_object($func[0])) { if (self::verifix_method($func, $strict, $bound, $reason)) { } elseif (self::verifix_static($func, $strict, $bound, $reason)) { } else { $valid = false; } } else { if (self::verifix_static($func, $strict, $bound, $reason)) { } elseif (self::verifix_method($func, $strict, $bound, $reason)) { } else { $valid = false; } } if ($valid) return new self($func, $bound, $reason); } if ($reason === null) { $msg = var_export($func, true); $reason = "$msg: not a callable"; } throw new ValueException($reason); } static function call($func, ...$args) { return self::with($func)->invoke($args); } ############################################################################# const TYPE_CLOSURE = 0, TYPE_SIMPLE = 1, TYPE_CLASS = 2, TYPE_STATIC = 3, TYPE_METHOD = 4; protected function __construct($func, bool $bound=false, ?string $reason=null) { $object = null; $prefixArgs = []; if ($func instanceof Closure) { $type = self::TYPE_CLOSURE; $object = $func; $reflection = new ReflectionFunction($func); } elseif ($func instanceof ReflectionFunction) { $type = self::TYPE_SIMPLE; $reflection = $func; } elseif ($func instanceof ReflectionClass) { $type = self::TYPE_CLASS; $reflection = $func; } elseif ($func instanceof ReflectionMethod) { $type = self::TYPE_STATIC; $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($c, $f); 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 bind($objectOrClass, bool $rebind=false): self { } function bindClass($objectOrClass, bool $rebind=false): self { } 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: /** @var ReflectionFunction $function */ $function = $this->reflection; return $function->invoke(...$args); case self::TYPE_STATIC: 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); } } }