modifs.mineures sans commentaires
This commit is contained in:
parent
20473ac512
commit
3d89a72029
609
wip/php/func.php
609
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,523 @@
|
|||
<?php
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class func: outils pour appeler des 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", ...]
|
||||
* - 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, ...]
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue