From e26cfdfc887bc39161c288c7af47e21d21e76d31 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 24 Jun 2024 10:51:19 +0400 Subject: [PATCH] modifs.mineures sans commentaires --- nur_src/co.php | 1 - src/wip/php/func.php | 523 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 src/wip/php/func.php diff --git a/nur_src/co.php b/nur_src/co.php index 8e08a59..6e5edaf 100644 --- a/nur_src/co.php +++ b/nur_src/co.php @@ -3,7 +3,6 @@ namespace nur; use nur\b\ui\IContent; use nur\b\ui\IPrintable; -use nur\sery\php\content\c; /** * Class co: affichage générique de données formatées avec {@link c} diff --git a/src/wip/php/func.php b/src/wip/php/func.php new file mode 100644 index 0000000..81b31e6 --- /dev/null +++ b/src/wip/php/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 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); + } + } +}