diff --git a/.idea/nulib.iml b/.idea/nulib.iml
index 265b084..4a9634a 100644
--- a/.idea/nulib.iml
+++ b/.idea/nulib.iml
@@ -6,6 +6,8 @@
+
+
diff --git a/composer.json b/composer.json
index 879eb3b..3150c95 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,8 @@
"autoload": {
"psr-4": {
"nulib\\": "php/src_base",
+ "nulib\\ref\\": "php/src_ref",
+ "nulib\\sys\\": "php/src_sys",
"nulib\\output\\": "php/src_output",
"nulib\\web\\": "php/src_web"
}
diff --git a/php/src_ref/sys/ref_func.php b/php/src_ref/sys/ref_func.php
new file mode 100644
index 0000000..12523f8
--- /dev/null
+++ b/php/src_ref/sys/ref_func.php
@@ -0,0 +1,12 @@
+ ["string", null, "Ne sélectionner que les méthode dont le nom commence par ce préfixe"],
+ "args" => ["?array", null, "Arguments avec lesquels appeler les méthodes"],
+ "static_only" => ["bool", false, "N'appeler que les méthodes statiques si un objet est spécifié"],
+ "include" => ["?array", 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" => ["?array", null, "Exclure les méthodes dont le nom correspond à ce motif, qui peut être une sous-chaine ou une expression régulière"],
+ ];
+}
diff --git a/php/src_sys/README.md b/php/src_sys/README.md
new file mode 100644
index 0000000..08ccfe2
--- /dev/null
+++ b/php/src_sys/README.md
@@ -0,0 +1,5 @@
+# nulib\sys
+
+Ce package contient des services généraux spécifiques à PHP
+
+-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
\ No newline at end of file
diff --git a/php/src_sys/func.php b/php/src_sys/func.php
new file mode 100644
index 0000000..d48287d
--- /dev/null
+++ b/php/src_sys/func.php
@@ -0,0 +1,430 @@
+ 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::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;
+ }
+
+ /**
+ * 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, $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 $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);
+ }
+ }
+}
diff --git a/php/tests/sys/funcTest.php b/php/tests/sys/funcTest.php
new file mode 100644
index 0000000..35f1f37
--- /dev/null
+++ b/php/tests/sys/funcTest.php
@@ -0,0 +1,247 @@
+"));
+ self::assertTrue(func::is_method("->xxx"));
+ self::assertFalse(func::is_method([]));
+ self::assertFalse(func::is_method([""]));
+ self::assertTrue(func::is_method(["->xxx"]));
+ self::assertTrue(func::is_method(["->xxx", "aaa"]));
+ self::assertFalse(func::is_method([null, "->"]));
+ self::assertTrue(func::is_method([null, "->yyy"]));
+ self::assertFalse(func::is_method(["xxx", "->"]));
+ self::assertTrue(func::is_method(["xxx", "->yyy"]));
+ self::assertTrue(func::is_method([null, "->yyy", "aaa"]));
+ self::assertTrue(func::is_method(["xxx", "->yyy", "aaa"]));
+ }
+
+ function testFix_method() {
+ $object = new \stdClass();
+ $func= null;
+ func::fix_method($func, $object);
+ self::assertSame(null, $func);
+ $func= "";
+ func::fix_method($func, $object);
+ self::assertSame("", $func);
+ $func= "->";
+ func::fix_method($func, $object);
+ self::assertSame("->", $func);
+ $func= "->xxx";
+ func::fix_method($func, $object);
+ self::assertSame([$object, "xxx"], $func);
+ $func= [];
+ func::fix_method($func, $object);
+ self::assertSame([], $func);
+ $func= [""];
+ func::fix_method($func, $object);
+ self::assertSame([""], $func);
+ $func= ["->xxx"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "xxx"], $func);
+ $func= ["->xxx", "aaa"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "xxx", "aaa"], $func);
+ $func= [null, "->"];
+ func::fix_method($func, $object);
+ self::assertSame([null, "->"], $func);
+ $func= [null, "->yyy"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "yyy"], $func);
+ $func= ["xxx", "->"];
+ func::fix_method($func, $object);
+ self::assertSame(["xxx", "->"], $func);
+ $func= ["xxx", "->yyy"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "yyy"], $func);
+ $func= [null, "->yyy", "aaa"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "yyy", "aaa"], $func);
+ $func= ["xxx", "->yyy", "aaa"];
+ func::fix_method($func, $object);
+ self::assertSame([$object, "yyy", "aaa"], $func);
+ }
+
+ function testCall() {
+ self::assertSame(36, func::call("func36"));
+ self::assertSame(12, func::call(TC::class."::method"));
+ self::assertSame(12, func::call([TC::class, "method"]));
+ $closure = function() {
+ return 21;
+ };
+ self::assertSame(21, func::call($closure));
+ }
+
+ function testCall_all() {
+ $c1 = new C1();
+ $c2 = new C2();
+ $c3 = new C3();
+
+ self::assertSameValues([11, 12], func::call_all(C1::class));
+ self::assertSameValues([11, 12, 21, 22], func::call_all($c1));
+ self::assertSameValues([13, 11, 12], func::call_all(C2::class));
+ self::assertSameValues([13, 23, 11, 12, 21, 22], func::call_all($c2));
+ self::assertSameValues([111, 13, 12], func::call_all(C3::class));
+ self::assertSameValues([111, 121, 13, 23, 12, 22], func::call_all($c3));
+
+ $options = "conf";
+ self::assertSameValues([11], func::call_all(C1::class, $options));
+ self::assertSameValues([11, 21], func::call_all($c1, $options));
+ self::assertSameValues([11], func::call_all(C2::class, $options));
+ self::assertSameValues([11, 21], func::call_all($c2, $options));
+ self::assertSameValues([111], func::call_all(C3::class, $options));
+ self::assertSameValues([111, 121], func::call_all($c3, $options));
+
+ $options = ["prefix" => "conf"];
+ self::assertSameValues([11], func::call_all(C1::class, $options));
+ self::assertSameValues([11, 21], func::call_all($c1, $options));
+ self::assertSameValues([11], func::call_all(C2::class, $options));
+ self::assertSameValues([11, 21], func::call_all($c2, $options));
+ self::assertSameValues([111], func::call_all(C3::class, $options));
+ self::assertSameValues([111, 121], func::call_all($c3, $options));
+
+ self::assertSameValues([11, 12], func::call_all($c1, ["include" => "x"]));
+ self::assertSameValues([11, 21], func::call_all($c1, ["include" => "y"]));
+ self::assertSameValues([11, 12, 21], func::call_all($c1, ["include" => ["x", "y"]]));
+
+ self::assertSameValues([21, 22], func::call_all($c1, ["exclude" => "x"]));
+ self::assertSameValues([12, 22], func::call_all($c1, ["exclude" => "y"]));
+ self::assertSameValues([22], func::call_all($c1, ["exclude" => ["x", "y"]]));
+
+ self::assertSameValues([12], func::call_all($c1, ["include" => "x", "exclude" => "y"]));
+ }
+
+ function testCons() {
+ $obj1 = func::cons(WoCons::class, 1, 2, 3);
+ self::assertInstanceOf(WoCons::class, $obj1);
+
+ $obj2 = func::cons(WithEmptyCons::class, 1, 2, 3);
+ self::assertInstanceOf(WithEmptyCons::class, $obj2);
+
+ $obj3 = func::cons(WithCons::class, 1, 2, 3);
+ self::assertInstanceOf(WithCons::class, $obj3);
+ self::assertSame(1, $obj3->first);
+ }
+ }
+
+ class WoCons {
+ }
+ class WithEmptyCons {
+ function __construct() {
+ }
+ }
+ class WithCons {
+ public $first;
+ function __construct($first) {
+ $this->first = $first;
+ }
+ }
+
+ class TC {
+ static function method() {
+ return 12;
+ }
+ }
+
+ class C1 {
+ static function confps1_xy() {
+ return 11;
+ }
+ static function ps2_x() {
+ return 12;
+ }
+ function confp1_y() {
+ return 21;
+ }
+ function p2() {
+ return 22;
+ }
+ }
+ class C2 extends C1 {
+ static function ps3() {
+ return 13;
+ }
+ function p3() {
+ return 23;
+ }
+ }
+ class C3 extends C2 {
+ static function confps1_xy() {
+ return 111;
+ }
+ function confp1_y() {
+ return 121;
+ }
+ }
+}