0; } elseif ($count > 1) { if (!array_key_exists(1, $func)) return false; return is_string($func[1]) && strlen($func[1]) > 0; } } return false; } /** * 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"] * * on assume que {@link is_static()}($func) retourne true * * @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) && substr($func, 0, 2) == "::") { $func = "$class$func"; return true; } elseif (is_array($func) && array_key_exists(0, $func)) { $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 { return is_string($method) && strlen($method) > 2 && substr($method, 0, 2) == "->"; } /** * tester si $func est une chaine de la forme "->method" ou un tableau de la * forme ["->method", ...] ou [anything, "->method", ...] */ static final function is_method($func): bool { if (is_string($func)) { return self::isam($func); } elseif (is_array($func) && array_key_exists(0, $func)) { if (self::isam($func[0])) { # ["->method", ...] return true; } if (array_key_exists(1, $func) && self::isam($func[1])) { # [anything, "->method", ...] return true; } } return false; } /** * 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, substr($func, 2)]; return true; } } elseif (is_array($func) && array_key_exists(0, $func)) { if (self::isam($func[0])) $func = array_merge([null], $func); if (count($func) > 1 && array_key_exists(1, $func) && self::isam($func[1])) { $func[0] = $object; $func[1] = substr($func[1], 2); 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::unexpected_type("callable", $func); } } 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::unexpected_type("callable", $func); } if ($rf->isVariadic()) { $minArgs = $maxArgs = -1; } else { $minArgs = $rf->getNumberOfRequiredParameters(); $maxArgs = $rf->getNumberOfParameters(); } return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs]; } static final function _fill(array $context, array &$args): void { $minArgs = $context[3]; $maxArgs = $context[4]; if ($maxArgs != -1) $args = array_slice($args, 0, $maxArgs); if ($minArgs != -1) { 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 CALL_ALL_SCHEMA = [ "prefix" => [null, null, "Ne sélectionner que les méthode dont le nom commence par ce préfixe"], "args" => [null, null, "Arguments avec lesquels appeler les méthodes"], "static_only" => [null, false, "N'appeler que les méthodes statiques si un objet est spécifié"], "include" => [null, null, "N'inclure que les méthodes dont le nom correspond à ce motif, qui peut être une sous-chaine ou une expression régulière"], "exclude" => [null, null, "Exclure les méthodes dont le nom correspond à ce motif, qui peut être une sous-chaine ou une expression régulière"], ]; const MASK_PS = ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC; 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; } /** * 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_SCHEMA} */ static function get_all($class_or_object, $options=null): array { md::ensure_schema($options, self::CALL_ALL_SCHEMA, null, false); $prefix = $options["prefix"]; $length = strlen($prefix); $args = A::with($options["args"]); $includes = A::with($options["include"]); $excludes = A::with($options["exclude"]); $methods = []; 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 = $options["static_only"]? self::MASK_PS: self::MASK_P; $expected = $options["static_only"]? self::METHOD_PS: self::METHOD_P; } else { throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet"); } foreach ($c->getMethods() as $m) { if (($m->getModifiers() & $mask) != $expected) continue; $name = $m->getName(); if (substr($name, 0, $length) != $prefix) continue; if (!self::matches($name, $includes, $excludes)) continue; $method = [$class_or_object, $name]; A::merge($method, $args); $methods[] = $method; } 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, $options=null): array { $methods = self::get_all($class_or_object, $options); $values = []; foreach ($methods as $method) { $args = null; self::fix_args($method, $args); $values[$method[1]] = self::call($method, ...$args); } return $values; } /** * tester si $func est une chaine de la forme "XXX" où XXX est une classe * valide, ou un tableau de la forme ["XXX", ...] * * NB: il est possible d'avoir {@link is_static()} et {@link is_class()} * vraies pour la même valeur. s'il faut supporter les deux cas, appeler * {@link is_class()} d'abord */ static final function is_class($class): bool { if (is_string($class)) { return class_exists($class); } elseif (is_array($class) && array_key_exists(0, $class)) { return class_exists($class[0]); } return false; } /** * en assumant que {@link is_class()} est vrai, si $class est un tableau de * plus de 1 éléments, alors déplacer les éléments supplémentaires au début de * $args. par exemple: * ~~~ * $class = ["class", "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) > 1) { $prefix_args = array_slice($class, 1); $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; } /** * 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); } } }