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); } } }