diff --git a/bbnurse b/bbnurse index 1c2ca73..0765ecd 100755 --- a/bbnurse +++ b/bbnurse @@ -37,9 +37,11 @@ function copy_mature() { srcdir="$MYDIR/src/$src" path="$src" fi + tsrcdir= if [ -n "$path" ]; then srcpackage="nur/sery/$path"; srcpackage="${srcpackage//\//\\}" destpackage="nulib/$path"; destpackage="${destpackage//\//\\}" + [ -d "$MYDIR/tests/$path" ] && tsrcdir="$MYDIR/tests/$path" else srcpackage='nur\sery' destpackage='nulib' @@ -59,10 +61,15 @@ function copy_mature() { case "$libname" in nulib) - if [ -n "$path" ]; then - destdir="$libdir/php/src_${path%%/*}" + if [[ "$path" == */* ]]; then + destdir="$libdir/php/src_${path%%/*}/${path#*/}" + [ -n "$tsrcdir" ] && tdestdir="$libdir/php/tests/$path" + elif [ -n "$path" ]; then + destdir="$libdir/php/src_$path" + [ -n "$tsrcdir" ] && tdestdir="$libdir/php/tests/$path" else destdir="$libdir/php/src_base" + [ -n "$tsrcdir" ] && tdestdir="$libdir/php/tests" fi if [ -n "$class" ]; then @@ -70,9 +77,8 @@ function copy_mature() { die "TODO" else # Maturation de toute un package - pwd #XXX + etitle "Maturation de $srcpackage vers $destpackage" einfo "$srcdir --> $destdir" - einfo "Maturation de $srcpackage vers $destpackage" setx -a srcs=find "$srcdir" -type f for src in "${srcs[@]}"; do srcname="${src#$srcdir/}" @@ -81,6 +87,18 @@ function copy_mature() { mkdirof "$dest" sed "s|${srcpackage//\\/\\\\}|${destpackage//\\/\\\\}|g" <"$src" >"$dest" done + if [ -n "$tsrcdir" ]; then + einfo "$tsrcdir --> $tdestdir" + setx -a srcs=find "$tsrcdir" -type f + for src in "${srcs[@]}"; do + srcname="${src#$tsrcdir/}" + dest="$tdestdir/$srcname" + estep "${src#$tsrcdir/}" + mkdirof "$dest" + sed "s|${srcpackage//\\/\\\\}|${destpackage//\\/\\\\}|g" <"$src" >"$dest" + done + fi + eend fi ;; esac diff --git a/src/schema/ref/ref_analyze.php b/src/ref/schema/ref_analyze.php similarity index 95% rename from src/schema/ref/ref_analyze.php rename to src/ref/schema/ref_analyze.php index 91c411f..b4980dd 100644 --- a/src/schema/ref/ref_analyze.php +++ b/src/ref/schema/ref_analyze.php @@ -1,5 +1,5 @@ ["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/src/schema/Schema.php b/src/schema/Schema.php index 8c58354..5ab9997 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -43,13 +43,13 @@ abstract class Schema implements ArrayAccess { * variable $dest (si $destKey===null) ou $dest[$destKey] si $destKey n'est * pas null */ - static function nv(?Value &$value=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value { + static function nv(?Value &$destv=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value { if ($definition === null) { # bien que techniquement, $definition peut être null (il s'agit alors du # schéma d'un scalaire quelconque), on ne l'autorise pas ici throw SchemaException::invalid_schema("definition is required"); } - return self::ns($schema, $definition)->newValue($value, $dest, $destKey); + return self::ns($schema, $definition)->newValue($destv, $dest, $destKey); } /** @@ -68,7 +68,7 @@ abstract class Schema implements ArrayAccess { /** retourner true si le schéma est de nature scalaire */ function isScalar(?ScalarSchema &$scalar=null): bool { return false; } - abstract function newValue(?Value &$value=null, &$dest=null, $destKey=null): Value; + abstract function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value; ############################################################################# # key & properties diff --git a/src/schema/_assoc/AssocSchema.php b/src/schema/_assoc/AssocSchema.php index dedb887..b6c20a0 100644 --- a/src/schema/_assoc/AssocSchema.php +++ b/src/schema/_assoc/AssocSchema.php @@ -45,10 +45,10 @@ class AssocSchema extends Schema { return true; } - function newValue(?Value &$value=null, &$dest=null, $destKey=null): Value { - if (!($value instanceof AssocValue)) $value = new AssocValue($this); + function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value { + if (!($destv instanceof AssocValue)) $destv = new AssocValue($this); if ($dest instanceof Input) $input = $dest; else $input = new Input($dest); - return $value->reset($input, $destKey); + return $destv->reset($input, $destKey); } } diff --git a/src/schema/_list/ListSchema.php b/src/schema/_list/ListSchema.php index 0f533c8..cda899a 100644 --- a/src/schema/_list/ListSchema.php +++ b/src/schema/_list/ListSchema.php @@ -45,8 +45,8 @@ class ListSchema extends Schema { return true; } - function newValue(?Value &$value=null, &$dest=null, $destKey=null): Value { - if (!($value instanceof ListValue)) $value = new ListValue($this); - return $value->reset($dest, $destKey); + function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value { + if (!($destv instanceof ListValue)) $destv = new ListValue($this); + return $destv->reset($dest, $destKey); } } diff --git a/src/schema/_scalar/ScalarResult.php b/src/schema/_scalar/ScalarResult.php index 7967b95..939235f 100644 --- a/src/schema/_scalar/ScalarResult.php +++ b/src/schema/_scalar/ScalarResult.php @@ -3,8 +3,8 @@ namespace nur\sery\schema\_scalar; use nulib\cl; use nulib\ValueException; -use nur\sery\schema\ref\ref_analyze; -use nur\sery\schema\ref\ref_schema; +use nur\sery\ref\schema\ref_analyze; +use nur\sery\ref\schema\ref_schema; use nur\sery\schema\Result; /** diff --git a/src/schema/_scalar/ScalarSchema.php b/src/schema/_scalar/ScalarSchema.php index 0c66088..4ca3a7c 100644 --- a/src/schema/_scalar/ScalarSchema.php +++ b/src/schema/_scalar/ScalarSchema.php @@ -2,8 +2,8 @@ namespace nur\sery\schema\_scalar; use nulib\cl; -use nur\sery\schema\ref\ref_schema; -use nur\sery\schema\ref\ref_types; +use nur\sery\ref\schema\ref_schema; +use nur\sery\ref\schema\ref_types; use nur\sery\schema\Schema; use nur\sery\schema\SchemaException; use nur\sery\schema\types\tarray; @@ -180,9 +180,9 @@ class ScalarSchema extends Schema { return true; } - function newValue(?Value &$value=null, &$dest=null, $destKey=null): Value { - if ($value instanceof ScalarValue) return $value->reset($dest, $destKey); - else return ($value = new ScalarValue($this, $dest, $destKey)); + function newValue(?Value &$destv=null, &$dest=null, $destKey=null): Value { + if ($destv instanceof ScalarValue) return $destv->reset($dest, $destKey); + else return ($destv = new ScalarValue($this, $dest, $destKey)); } ############################################################################# diff --git a/src/schema/_scalar/ScalarValue.php b/src/schema/_scalar/ScalarValue.php index 7402eb1..06b0d86 100644 --- a/src/schema/_scalar/ScalarValue.php +++ b/src/schema/_scalar/ScalarValue.php @@ -3,7 +3,7 @@ namespace nur\sery\schema\_scalar; use nulib\ValueException; use nur\sery\schema\input\Input; -use nur\sery\schema\ref\ref_analyze; +use nur\sery\ref\schema\ref_analyze; use nur\sery\schema\Result; use nur\sery\schema\types; use nur\sery\schema\types\IType; @@ -11,6 +11,11 @@ use nur\sery\schema\Value; class ScalarValue extends Value { function __construct(ScalarSchema $schema, &$dest=null, $destKey=null, bool $defaultVerifix=true, ?bool $defaultThrow=null) { + if ($dest !== null && $defaultThrow = null) { + # Si $dest est null, ne pas lancer d'exception, parce qu'on considère que + # c'est une initialisation sans conséquences + $defaultThrow = true; + } $this->schema = $schema; $this->defaultVerifix = $defaultVerifix; $this->defaultThrow = $defaultThrow !== null? $defaultThrow: false; diff --git a/src/schema/input/FormInput.php b/src/schema/input/FormInput.php index 0013d68..80b1c97 100644 --- a/src/schema/input/FormInput.php +++ b/src/schema/input/FormInput.php @@ -9,7 +9,6 @@ namespace nur\sery\schema\input; * * cette implémentation lit depuis les paramètres de formulaire et écrit dans * une référence - * */ class FormInput extends Input { const ALLOW_EMPTY = false; diff --git a/src/schema/input/Input.php b/src/schema/input/Input.php index 2012a55..701c1b3 100644 --- a/src/schema/input/Input.php +++ b/src/schema/input/Input.php @@ -1,9 +1,6 @@ 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/src/values/mprop.php b/src/values/mprop.php index 3791010..354b25a 100644 --- a/src/values/mprop.php +++ b/src/values/mprop.php @@ -3,6 +3,7 @@ namespace nur\sery\values; use nulib\cl; use nulib\str; +use nur\sery\sys\func; use ReflectionClass; use ReflectionException; diff --git a/tests/sys/funcTest.php b/tests/sys/funcTest.php new file mode 100644 index 0000000..226f514 --- /dev/null +++ b/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; + } + } +}