diff --git a/wip/php/func.php b/wip/php/func.php index 81b31e6..688163d 100644 --- a/wip/php/func.php +++ b/wip/php/func.php @@ -2,522 +2,155 @@ namespace nur\sery\wip\php; use Closure; -use nur\sery\cl; -use nur\sery\ref\php\ref_func; -use nur\sery\str; -use nur\sery\ValueException; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; +use nur\sery\cv; /** - * Class func: outils pour appeler des fonctions et méthodes dynamiquement + * Class func: outils pour appeler fonctions et méthodes dynamiquement * - * Les formats supportés sont: - * - fonctions globales - * - "func" si function_exists("func") - * - [false, "func", ...] - * - méthodes statiques - * - "::method" méthode à lier à une classe avant l'appel - * - "class::method" - * - ["method"] si !class_exists("method") - * - [null, "method", ...] méthode à lier à une classe avant l'appel - * - ["class", "method", ...] + * les fonctions supportées sont: + * - fonctions simples (globales ou dans un namespace) + * - fonctions statiques * - méthodes - * - "->method" méthode à lier à un objet avant l'appel - * - ["method"] si !class_exists("method") - * - [null, "method", ...] méthode à lier à un objet avant l'appel - * - [$object, "method", ...] - * - classes - * - "class" si !function_exists("class") - * - "class::" - * - ["class"] si class_exists("class") - * - ["class", null, ...] + * - Closure + * - constructeur (l'appel de cette "fonction" provoque l'instanciation d'un + * objet) * - * les formes "func" et "class" sont distinguées en vérifiant l'existence de la - * fonction - * - * les formes ["class"] et ["method"] sont distinguées en vérifiant l'existence - * de la classe + * les fonctions statiques et les méthodes peuvent être liées (associées à une + * classe ou à un objet) ou non liées (il faut les lier avant de pouvoir les + * utiliser) */ class func { - /** tester si $value est une chaine non vide */ - private static function is_ne($value): bool { - return is_string($value) && strlen($value) > 0; - } - /** - * tester si $func est d'une des formes suivantes: - * - "func" si function_exists("func") - * - [false, "func", ...] + * 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 final function is_global($func): bool { - if (self::is_ne($func)) { - $pos = strpos($func, "::"); - return $pos === false && function_exists($func); + static function verifix_simple(&$func, bool $strict=true): bool { + if (is_string($func)) { + $func = [false, $func]; } elseif (is_array($func)) { - return ($func[0] ?? null) === false - && self::is_ne($func[1] ?? null); - } - return false; - } - - static final function fix_global_args(&$func, ?array &$args): bool { - if ($args === null) $args = []; - if (is_array($func)) { - if (count($func) > 2) { - $prefix_args = array_slice($func, 2); - $func = array_slice($func, 1, 1)[0]; - $args = array_merge($prefix_args, $args); - } else { - $func = $func[0]; - } - return true; - } - return false; - } - - /** - * tester si $func est d'une des formes suivantes: - * - "::method" - * - "class::method" - * - ["method"] si !class_exists("method") - * - [anything, "method", ...] - */ - static final function is_static($func): bool { - if (self::is_ne($func)) { - $pos = strpos($func, "::"); - return $pos !== false && $pos + 2 < strlen($func); - } elseif (is_array($func) && array_key_exists(0, $func)) { - $count = count($func); - if ($count == 1) { - return self::is_ne($func[0]) && !class_exists($func[0]); - } elseif ($count > 1 && array_key_exists(1, $func)) { - return self::is_ne($func[1]); - } - } - return false; - } - - /** - * en assumant que {@link self::is_static()} retourne true: - * - si $func est une chaine de la forme "::method" alors la remplacer par la - * chaine "$class::method" - * - si $func est un tableau de la forme ["method"] ou [null, "method"], alors - * le remplacer par [$class, "method"] - * - * @return bool true si la correction a été faite - */ - static final function fix_static(&$func, $class): bool { - if (is_object($class)) $class = get_class($class); - - if (is_string($func)) { - if (substr($func, 0, 2) == "::") { - $func = "$class$func"; - return true; - } + if (!array_key_exists(0, $func)) return false; + if ($func[0] !== false) return false; + if (!array_key_exists(1, $func)) return false; + if (!is_string($func[1])) return false; } else { - $count = count($func); - if ($count == 1) { - $func = [$class, $func[0]]; - return true; - } elseif ($count > 1 && $func[0] === null) { - $func[0] = $class; - return true; - } + return false; } - return false; - } - - /** tester si $method est une chaine de la forme "->method" */ - private static function isam(&$method, bool $requireArrow=false): bool { - if (is_string($method)) { - if (substr($method, 0, 2) == "->") { - $method = substr($method, 2); - } elseif ($requireArrow) { - return false; - } - return strlen($method) > 0; - } - return false; - } - - /** - * tester si $func est d'une des formes suivantes: - * - "->method" - * - ["method"] si !class_exists("method") - * - [anything, "method", ...] - */ - static final function is_method($func): bool { - if (is_string($func)) { - return self::isam($func, true); - } elseif (is_array($func) && array_key_exists(0, $func)) { - $count = count($func); - if ($count == 1) { - return self::isam($func[0]) && !class_exists($func[0]); - } elseif ($count > 1 && array_key_exists(1, $func)) { - return self::isam($func[1]); - } - } - return false; - } - - /** - * en assumant que {@link self::is_method()} retourne true: - * - si $func est une chaine de la forme "->method" alors la remplacer par le - * tableau [$object, "method"] - * - si $func est un tableau de la forme ["method"] ou [anything, "method"], - * alors le remplacer par [$object, "method"] - * - * @return bool true si la correction a été faite - */ - static final function fix_method(&$func, $object): bool { - if (!is_object($object)) return false; - - if (is_string($func)) { - if (self::isam($func)) { - $func = [$object, $func]; - return true; - } - } else { - $count = count($func); - if ($count == 1) { - self::isam($func[0]); - $func = [$object, $func[0]]; - return true; - } else { - $func[0] = $object; - self::isam($func[1]); - return true; - } - } - return false; - } - - /** - * si $func est un tableau de plus de 2 éléments, alors déplacer les éléments - * supplémentaires au début de $args. par exemple: - * ~~~ - * $func = ["class", "method", "arg1", "arg2"]; - * $args = ["arg3"]; - * func::fix_args($func, $args) - * # $func === ["class", "method"] - * # $args === ["arg1", "arg2", "arg3"] - * ~~~ - * - * @return bool true si la correction a été faite - */ - static final function fix_args(&$func, ?array &$args): bool { - if ($args === null) $args = []; - if (is_array($func) && count($func) > 2) { - $prefix_args = array_slice($func, 2); - $func = array_slice($func, 0, 2); - $args = array_merge($prefix_args, $args); - return true; - } - return false; - } - - /** - * s'assurer que $func est un appel de méthode ou d'une méthode statique; - * et renseigner le cas échéant les arguments. si $func ne fait pas mention - * de la classe ou de l'objet, le renseigner avec $class_or_object. - * - * @return bool true si c'est une fonction valide. il ne reste plus qu'à - * l'appeler avec {@link call()} - */ - static final function check_func(&$func, $class_or_object, &$args=null): bool { - if ($func instanceof Closure) return true; - if (self::is_method($func)) { - # méthode - self::fix_method($func, $class_or_object); - self::fix_args($func, $args); - return true; - } elseif (self::is_static($func)) { - # méthode statique - self::fix_static($func, $class_or_object); - self::fix_args($func, $args); - return true; - } - return false; - } - - /** - * Comme {@link check_func()} mais lance une exception si la fonction est - * invalide - * - * @throws ValueException si $func n'est pas une fonction ou une méthode valide - */ - static final function ensure_func(&$func, $class_or_object, &$args=null): void { - if (!self::check_func($func, $class_or_object, $args)) { - throw ValueException::invalid_type($func, "callable"); - } - } - - static final function _prepare($func): array { - $object = null; - if (is_callable($func)) { - if (is_array($func)) { - $rf = new ReflectionMethod(...$func); - $object = $func[0]; - if (is_string($object)) $object = null; - } elseif ($func instanceof Closure) { - $rf = new ReflectionFunction($func); - } elseif (is_string($func) && strpos($func, "::") === false) { - $rf = new ReflectionFunction($func); - } else { - $rf = new ReflectionMethod($func); - } - } elseif ($func instanceof ReflectionMethod) { - $rf = $func; - } elseif ($func instanceof ReflectionFunction) { - $rf = $func; - } elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1]) - && ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) { - $object = $func[0]; - if (is_string($object)) $object = null; - $rf = $func[1]; - } elseif (is_string($func) && strpos($func, "::") === false) { - $rf = new ReflectionFunction($func); - } else { - throw ValueException::invalid_type($func, "callable"); - } - $minArgs = $rf->getNumberOfRequiredParameters(); - $maxArgs = $rf->getNumberOfParameters(); - $variadic = $rf->isVariadic(); - return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic]; - } - - static final function _fill(array $context, array &$args): void { - $minArgs = $context[3]; - $maxArgs = $context[4]; - $variadic = $context[5]; - if (!$variadic) $args = array_slice($args, 0, $maxArgs); - while (count($args) < $minArgs) $args[] = null; - } - - static final function _call($context, array $args) { - self::_fill($context, $args); - $use_object = $context[0]; - $object = $context[1]; - $method = $context[2]; - if ($use_object) { - if (count($args) === 0) return $method->invoke($object); - else return $method->invokeArgs($object, $args); - } else { - if (count($args) === 0) return $method->invoke(); - else return $method->invokeArgs($args); - } - } - - /** - * Appeler la fonction spécifiée avec les arguments spécifiés. - * Adapter $args en fonction du nombre réel d'arguments de $func - * - * @param callable|ReflectionFunction|ReflectionMethod $func - */ - static final function call($func, ...$args) { - return self::_call(self::_prepare($func), $args); - } - - /** remplacer $value par $func($value, ...$args) */ - static final function apply(&$value, $func, ...$args): void { - if ($func !== null) { - if ($args) $args = array_merge([$value], $args); - else $args = [$value]; - $value = self::call($func, ...$args); - } - } - - const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC; - const MASK_P = ReflectionMethod::IS_PUBLIC; - const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC; - const METHOD_P = ReflectionMethod::IS_PUBLIC; - - private static final function matches(string $name, array $includes, array $excludes): bool { - if ($includes) { - $matches = false; - foreach ($includes as $include) { - if (substr($include, 0, 1) == "/") { - # expression régulière - if (preg_match($include, $name)) { - $matches = true; - break; - } - } else { - # tester la présence de la sous-chaine - if (strpos($name, $include) !== false) { - $matches = true; - break; - } - } - } - if (!$matches) return false; - } - foreach ($excludes as $exclude) { - if (substr($exclude, 0, 1) == "/") { - # expression régulière - if (preg_match($exclude, $name)) return false; - } else { - # tester la présence de la sous-chaine - if (strpos($name, $exclude) !== false) return false; - } + $f = $func[1]; + if (strpos($f, "::") !== false) return false; + if (strpos($f, "->") !== false) return false; + if ($strict) { + if (class_exists($f)) return false; + if (!function_exists($f)) return false; } return true; } - /** @var Schema */ - private static $call_all_params_schema; + private static function parse_class(string $cf, ?string &$c, ?string &$f): bool { + if (strpos($cf, "->") !== false) return false; + if ($cf === "::") return false; + $pos = strpos($cf, "::"); + if ($pos == 0) return false; # inclus $pos === false + $c = substr($cf, 0, $pos); + $f = cv::vn(substr($cf, $pos + 2)); + return true; + } - /** - * retourner la liste des méthodes de $class_or_object qui correspondent au - * filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA} - */ - static function get_all($class_or_object, $params=null): array { - Schema::nv($paramsv, $params, null - , self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA); - if (is_callable($class_or_object, true) && is_array($class_or_object)) { - # callable sous forme de tableau - $class_or_object = $class_or_object[0]; - } - if (is_string($class_or_object)) { - # lister les méthodes publiques statiques de la classe - $mask = self::MASK_PS; - $expected = self::METHOD_PS; - $c = new ReflectionClass($class_or_object); - } elseif (is_object($class_or_object)) { - # lister les méthodes publiques de la classe - $c = new ReflectionClass($class_or_object); - $mask = $params["static_only"]? self::MASK_PS: self::MASK_P; - $expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P; + private static function parse_static(string $cf, ?string &$c, ?string &$f, ?bool &$bound): bool { + if (strpos($cf, "->") !== false) return false; + if ($cf === "::") return false; + $pos = strpos($cf, "::"); + if ($pos === false) return false; + if ($pos == strlen($cf) - 2) return false; + if ($pos > 0) { + $c = substr($cf, 0, $pos); + $bound = true; } else { - throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); + $c = null; + $bound = false; } - $prefix = $params["prefix"]; $prefixlen = strlen($prefix); - $args = $params["args"]; - $includes = $params["include"]; - $excludes = $params["exclude"]; - $methods = []; - foreach ($c->getMethods() as $m) { - if (($m->getModifiers() & $mask) != $expected) continue; - $name = $m->getName(); - if (substr($name, 0, $prefixlen) != $prefix) continue; - if (!self::matches($name, $includes, $excludes)) continue; - $methods[] = cl::merge([$class_or_object, $name], $args); - } - return $methods; + $f = substr($cf, $pos + 2); + return true; } /** - * Appeler toutes les méthodes publiques de $object_or_class et retourner un - * tableau [$method_name => $return_value] des valeurs de retour. - */ - static final function call_all($class_or_object, $params=null): array { - $methods = self::get_all($class_or_object, $params); - $values = []; - foreach ($methods as $method) { - self::fix_args($method, $args); - $values[$method[1]] = self::call($method, ...$args); - } - return $values; - } - - /** - * tester si $class est d'une des formes suivantes: - * - "class" si !function_exists("class") - * - "class::" - * - ["class"] si class_exists("class") - * - ["class", null, ...] - */ - static final function is_class($class): bool { - if (self::is_ne($class)) { - return str::ends_with("::", $class) - || !function_exists($class); - } elseif (is_array($class) && self::is_ne($class[0] ?? null)) { - $count = count($class); - if ($count == 1) { - return class_exists($class[0]); - } elseif ($count > 1 && array_key_exists(1, $class)) { - return $class[1] === null; - } - } - return false; - } - - /** - * si $class est un tableau de plus de 2 éléments, alors déplacer les éléments - * supplémentaires au début de $args. par exemple: - * ~~~ - * $class = ["class", null, "arg1", "arg2"]; - * $args = ["arg3"]; - * func::fix_class_args($class, $args) - * # $class === "class" - * # $args === ["arg1", "arg2", "arg3"] - * ~~~ + * vérifier que $func est une fonction statique, et la normaliser le cas + * échéant. retourner true si c'est une fonction statique, false sinon * - * @return bool true si la correction a été faite - */ - static final function fix_class_args(&$class, ?array &$args): bool { - if ($args === null) $args = []; - if (is_array($class)) { - if (count($class) > 2) { - $prefix_args = array_slice($class, 2); - $class = array_slice($class, 0, 1)[0]; - $args = array_merge($prefix_args, $args); - } else { - $class = $class[0]; - } - return true; - } - return false; - } - - /** - * s'assurer que $class est une classe et renseigner le cas échéant les - * arguments. + * les formes suivantes sont supportées (XXX étant null ou n'importe quelle + * valeur scalaire de n'importe quel type) + * - "XXX::function" + * - ["XXX::function", ...$args] + * - [XXX, "::function", ...$args] + * - [XXX, "function", ...$args] c'est la forme normalisée * - * @return bool true si c'est une classe valide. il ne reste plus qu'à - * l'instancier avec {@link cons()} - */ - static final function check_class(&$class, &$args=null): bool { - if (self::is_class($class)) { - self::fix_class_args($class, $args); - return true; - } - return false; - } - - /** - * Comme {@link check_class()} mais lance une exception si la classe est - * invalide + * Si XXX est une classe, la fonction statique est liée. sinon, elle doit être + * liée à une classe avant d'être utilisée * - * @throws ValueException si $class n'est pas une classe valide + * @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 final function ensure_class(&$class, &$args=null): void { - if (!self::check_class($class, $args)) { - throw ValueException::invalid_type($class, "class"); - } - } - - /** - * Instancier la classe avec les arguments spécifiés. - * Adapter $args en fonction du nombre réel d'arguments du constructeur - */ - static final function cons(string $class, ...$args) { - $c = new ReflectionClass($class); - $rf = $c->getConstructor(); - if ($rf === null) { - return $c->newInstance(); - } else { - if (!$rf->isVariadic()) { - $minArgs = $rf->getNumberOfRequiredParameters(); - $maxArgs = $rf->getNumberOfParameters(); - $args = array_slice($args, 0, $maxArgs); - while (count($args) < $minArgs) { - $args[] = null; + static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null): bool { + if (is_string($func)) { + if (!self::parse_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 (self::parse_class($c, $c, $f)) { + if ($f !== null) { + # ["class::method"] + array_splice($func, 1, 0, [$f]); } + $bound = true; + } else { + $bound = false; } - return $c->newInstanceArgs($args); + # + if (!array_key_exists(1, $func)) return false; + $f = $func[1]; + if (!is_string($f)) return false; + if (!self::parse_static($f, $rc, $f, $rbound)) return false; + if ($rc !== null && $c === null) { + $c = $rc; + $bound = $rbound; + } + $func[0] = $c; + $func[1] = $f; + } else { + return false; + } + if ($strict && !method_exists($c, $f)) return false; + return true; + } + + static function verifix_method(&$func): bool { + if (is_string($func)) { + + return true; + } elseif (is_array($func)) { + + return true; + } else { + return false; + } + } + + static function verifix_cons(&$func): bool { + if (is_string($func)) { + + return true; + } elseif (is_array($func)) { + + return true; + } else { + return false; } } } diff --git a/wip/php/orig_func.php b/wip/php/orig_func.php new file mode 100644 index 0000000..773d355 --- /dev/null +++ b/wip/php/orig_func.php @@ -0,0 +1,523 @@ +method" méthode à lier à un objet avant l'appel + * - ["method"] si !class_exists("method") + * - [null, "method", ...] méthode à lier à un objet avant l'appel + * - [$object, "method", ...] + * - classes + * - "class" si !function_exists("class") + * - "class::" + * - ["class"] si class_exists("class") + * - ["class", null, ...] + * + * les formes "func" et "class" sont distinguées en vérifiant l'existence de la + * fonction + * + * les formes ["class"] et ["method"] sont distinguées en vérifiant l'existence + * de la classe + */ +class orig_func { + /** tester si $value est une chaine non vide */ + private static function is_ne($value): bool { + return is_string($value) && strlen($value) > 0; + } + + /** + * tester si $func est d'une des formes suivantes: + * - "func" si function_exists("func") + * - [false, "func", ...] + */ + static final function is_global($func): bool { + if (self::is_ne($func)) { + $pos = strpos($func, "::"); + return $pos === false && function_exists($func); + } elseif (is_array($func)) { + return ($func[0] ?? null) === false + && self::is_ne($func[1] ?? null); + } + return false; + } + + static final function fix_global_args(&$func, ?array &$args): bool { + if ($args === null) $args = []; + if (is_array($func)) { + if (count($func) > 2) { + $prefix_args = array_slice($func, 2); + $func = array_slice($func, 1, 1)[0]; + $args = array_merge($prefix_args, $args); + } else { + $func = $func[0]; + } + return true; + } + return false; + } + + /** + * tester si $func est d'une des formes suivantes: + * - "::method" + * - "class::method" + * - ["method"] si !class_exists("method") + * - [anything, "method", ...] + */ + static final function is_static($func): bool { + if (self::is_ne($func)) { + $pos = strpos($func, "::"); + return $pos !== false && $pos + 2 < strlen($func); + } elseif (is_array($func) && array_key_exists(0, $func)) { + $count = count($func); + if ($count == 1) { + return self::is_ne($func[0]) && !class_exists($func[0]); + } elseif ($count > 1 && array_key_exists(1, $func)) { + return self::is_ne($func[1]); + } + } + return false; + } + + /** + * en assumant que {@link self::is_static()} retourne true: + * - si $func est une chaine de la forme "::method" alors la remplacer par la + * chaine "$class::method" + * - si $func est un tableau de la forme ["method"] ou [null, "method"], alors + * le remplacer par [$class, "method"] + * + * @return bool true si la correction a été faite + */ + static final function fix_static(&$func, $class): bool { + if (is_object($class)) $class = get_class($class); + + if (is_string($func)) { + if (substr($func, 0, 2) == "::") { + $func = "$class$func"; + return true; + } + } else { + $count = count($func); + if ($count == 1) { + $func = [$class, $func[0]]; + return true; + } elseif ($count > 1 && $func[0] === null) { + $func[0] = $class; + return true; + } + } + return false; + } + + /** tester si $method est une chaine de la forme "->method" */ + private static function isam(&$method, bool $requireArrow=false): bool { + if (is_string($method)) { + if (substr($method, 0, 2) == "->") { + $method = substr($method, 2); + } elseif ($requireArrow) { + return false; + } + return strlen($method) > 0; + } + return false; + } + + /** + * tester si $func est d'une des formes suivantes: + * - "->method" + * - ["method"] si !class_exists("method") + * - [anything, "method", ...] + */ + static final function is_method($func): bool { + if (is_string($func)) { + return self::isam($func, true); + } elseif (is_array($func) && array_key_exists(0, $func)) { + $count = count($func); + if ($count == 1) { + return self::isam($func[0]) && !class_exists($func[0]); + } elseif ($count > 1 && array_key_exists(1, $func)) { + return self::isam($func[1]); + } + } + return false; + } + + /** + * en assumant que {@link self::is_method()} retourne true: + * - si $func est une chaine de la forme "->method" alors la remplacer par le + * tableau [$object, "method"] + * - si $func est un tableau de la forme ["method"] ou [anything, "method"], + * alors le remplacer par [$object, "method"] + * + * @return bool true si la correction a été faite + */ + static final function fix_method(&$func, $object): bool { + if (!is_object($object)) return false; + + if (is_string($func)) { + if (self::isam($func)) { + $func = [$object, $func]; + return true; + } + } else { + $count = count($func); + if ($count == 1) { + self::isam($func[0]); + $func = [$object, $func[0]]; + return true; + } else { + $func[0] = $object; + self::isam($func[1]); + return true; + } + } + return false; + } + + /** + * si $func est un tableau de plus de 2 éléments, alors déplacer les éléments + * supplémentaires au début de $args. par exemple: + * ~~~ + * $func = ["class", "method", "arg1", "arg2"]; + * $args = ["arg3"]; + * func::fix_args($func, $args) + * # $func === ["class", "method"] + * # $args === ["arg1", "arg2", "arg3"] + * ~~~ + * + * @return bool true si la correction a été faite + */ + static final function fix_args(&$func, ?array &$args): bool { + if ($args === null) $args = []; + if (is_array($func) && count($func) > 2) { + $prefix_args = array_slice($func, 2); + $func = array_slice($func, 0, 2); + $args = array_merge($prefix_args, $args); + return true; + } + return false; + } + + /** + * s'assurer que $func est un appel de méthode ou d'une méthode statique; + * et renseigner le cas échéant les arguments. si $func ne fait pas mention + * de la classe ou de l'objet, le renseigner avec $class_or_object. + * + * @return bool true si c'est une fonction valide. il ne reste plus qu'à + * l'appeler avec {@link call()} + */ + static final function check_func(&$func, $class_or_object, &$args=null): bool { + if ($func instanceof Closure) return true; + if (self::is_method($func)) { + # méthode + self::fix_method($func, $class_or_object); + self::fix_args($func, $args); + return true; + } elseif (self::is_static($func)) { + # méthode statique + self::fix_static($func, $class_or_object); + self::fix_args($func, $args); + return true; + } + return false; + } + + /** + * Comme {@link check_func()} mais lance une exception si la fonction est + * invalide + * + * @throws ValueException si $func n'est pas une fonction ou une méthode valide + */ + static final function ensure_func(&$func, $class_or_object, &$args=null): void { + if (!self::check_func($func, $class_or_object, $args)) { + throw ValueException::invalid_type($func, "callable"); + } + } + + static final function _prepare($func): array { + $object = null; + if (is_callable($func)) { + if (is_array($func)) { + $rf = new ReflectionMethod(...$func); + $object = $func[0]; + if (is_string($object)) $object = null; + } elseif ($func instanceof Closure) { + $rf = new ReflectionFunction($func); + } elseif (is_string($func) && strpos($func, "::") === false) { + $rf = new ReflectionFunction($func); + } else { + $rf = new ReflectionMethod($func); + } + } elseif ($func instanceof ReflectionMethod) { + $rf = $func; + } elseif ($func instanceof ReflectionFunction) { + $rf = $func; + } elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1]) + && ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) { + $object = $func[0]; + if (is_string($object)) $object = null; + $rf = $func[1]; + } elseif (is_string($func) && strpos($func, "::") === false) { + $rf = new ReflectionFunction($func); + } else { + throw ValueException::invalid_type($func, "callable"); + } + $minArgs = $rf->getNumberOfRequiredParameters(); + $maxArgs = $rf->getNumberOfParameters(); + $variadic = $rf->isVariadic(); + return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic]; + } + + static final function _fill(array $context, array &$args): void { + $minArgs = $context[3]; + $maxArgs = $context[4]; + $variadic = $context[5]; + if (!$variadic) $args = array_slice($args, 0, $maxArgs); + while (count($args) < $minArgs) $args[] = null; + } + + static final function _call($context, array $args) { + self::_fill($context, $args); + $use_object = $context[0]; + $object = $context[1]; + $method = $context[2]; + if ($use_object) { + if (count($args) === 0) return $method->invoke($object); + else return $method->invokeArgs($object, $args); + } else { + if (count($args) === 0) return $method->invoke(); + else return $method->invokeArgs($args); + } + } + + /** + * Appeler la fonction spécifiée avec les arguments spécifiés. + * Adapter $args en fonction du nombre réel d'arguments de $func + * + * @param callable|ReflectionFunction|ReflectionMethod $func + */ + static final function call($func, ...$args) { + return self::_call(self::_prepare($func), $args); + } + + /** remplacer $value par $func($value, ...$args) */ + static final function apply(&$value, $func, ...$args): void { + if ($func !== null) { + if ($args) $args = array_merge([$value], $args); + else $args = [$value]; + $value = self::call($func, ...$args); + } + } + + const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC; + const MASK_P = ReflectionMethod::IS_PUBLIC; + const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC; + const METHOD_P = ReflectionMethod::IS_PUBLIC; + + private static final function matches(string $name, array $includes, array $excludes): bool { + if ($includes) { + $matches = false; + foreach ($includes as $include) { + if (substr($include, 0, 1) == "/") { + # expression régulière + if (preg_match($include, $name)) { + $matches = true; + break; + } + } else { + # tester la présence de la sous-chaine + if (strpos($name, $include) !== false) { + $matches = true; + break; + } + } + } + if (!$matches) return false; + } + foreach ($excludes as $exclude) { + if (substr($exclude, 0, 1) == "/") { + # expression régulière + if (preg_match($exclude, $name)) return false; + } else { + # tester la présence de la sous-chaine + if (strpos($name, $exclude) !== false) return false; + } + } + return true; + } + + /** @var Schema */ + private static $call_all_params_schema; + + /** + * retourner la liste des méthodes de $class_or_object qui correspondent au + * filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA} + */ + static function get_all($class_or_object, $params=null): array { + Schema::nv($paramsv, $params, null + , self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA); + if (is_callable($class_or_object, true) && is_array($class_or_object)) { + # callable sous forme de tableau + $class_or_object = $class_or_object[0]; + } + if (is_string($class_or_object)) { + # lister les méthodes publiques statiques de la classe + $mask = self::MASK_PS; + $expected = self::METHOD_PS; + $c = new ReflectionClass($class_or_object); + } elseif (is_object($class_or_object)) { + # lister les méthodes publiques de la classe + $c = new ReflectionClass($class_or_object); + $mask = $params["static_only"]? self::MASK_PS: self::MASK_P; + $expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P; + } else { + throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); + } + $prefix = $params["prefix"]; $prefixlen = strlen($prefix); + $args = $params["args"]; + $includes = $params["include"]; + $excludes = $params["exclude"]; + $methods = []; + foreach ($c->getMethods() as $m) { + if (($m->getModifiers() & $mask) != $expected) continue; + $name = $m->getName(); + if (substr($name, 0, $prefixlen) != $prefix) continue; + if (!self::matches($name, $includes, $excludes)) continue; + $methods[] = cl::merge([$class_or_object, $name], $args); + } + return $methods; + } + + /** + * Appeler toutes les méthodes publiques de $object_or_class et retourner un + * tableau [$method_name => $return_value] des valeurs de retour. + */ + static final function call_all($class_or_object, $params=null): array { + $methods = self::get_all($class_or_object, $params); + $values = []; + foreach ($methods as $method) { + self::fix_args($method, $args); + $values[$method[1]] = self::call($method, ...$args); + } + return $values; + } + + /** + * tester si $class est d'une des formes suivantes: + * - "class" si !function_exists("class") + * - "class::" + * - ["class"] si class_exists("class") + * - ["class", null, ...] + */ + static final function is_class($class): bool { + if (self::is_ne($class)) { + return str::ends_with("::", $class) + || !function_exists($class); + } elseif (is_array($class) && self::is_ne($class[0] ?? null)) { + $count = count($class); + if ($count == 1) { + return class_exists($class[0]); + } elseif ($count > 1 && array_key_exists(1, $class)) { + return $class[1] === null; + } + } + return false; + } + + /** + * si $class est un tableau de plus de 2 éléments, alors déplacer les éléments + * supplémentaires au début de $args. par exemple: + * ~~~ + * $class = ["class", null, "arg1", "arg2"]; + * $args = ["arg3"]; + * func::fix_class_args($class, $args) + * # $class === "class" + * # $args === ["arg1", "arg2", "arg3"] + * ~~~ + * + * @return bool true si la correction a été faite + */ + static final function fix_class_args(&$class, ?array &$args): bool { + if ($args === null) $args = []; + if (is_array($class)) { + if (count($class) > 2) { + $prefix_args = array_slice($class, 2); + $class = array_slice($class, 0, 1)[0]; + $args = array_merge($prefix_args, $args); + } else { + $class = $class[0]; + } + return true; + } + return false; + } + + /** + * s'assurer que $class est une classe et renseigner le cas échéant les + * arguments. + * + * @return bool true si c'est une classe valide. il ne reste plus qu'à + * l'instancier avec {@link cons()} + */ + static final function check_class(&$class, &$args=null): bool { + if (self::is_class($class)) { + self::fix_class_args($class, $args); + return true; + } + return false; + } + + /** + * Comme {@link check_class()} mais lance une exception si la classe est + * invalide + * + * @throws ValueException si $class n'est pas une classe valide + */ + static final function ensure_class(&$class, &$args=null): void { + if (!self::check_class($class, $args)) { + throw ValueException::invalid_type($class, "class"); + } + } + + /** + * Instancier la classe avec les arguments spécifiés. + * Adapter $args en fonction du nombre réel d'arguments du constructeur + */ + static final function cons(string $class, ...$args) { + $c = new ReflectionClass($class); + $rf = $c->getConstructor(); + if ($rf === null) { + return $c->newInstance(); + } else { + if (!$rf->isVariadic()) { + $minArgs = $rf->getNumberOfRequiredParameters(); + $maxArgs = $rf->getNumberOfParameters(); + $args = array_slice($args, 0, $maxArgs); + while (count($args) < $minArgs) { + $args[] = null; + } + } + return $c->newInstanceArgs($args); + } + } +}