595 lines
18 KiB
PHP
595 lines
18 KiB
PHP
<?php
|
|
namespace nur\sery\wip\php;
|
|
|
|
use Closure;
|
|
use nur\sery\cv;
|
|
use nur\sery\StateException;
|
|
use nur\sery\ValueException;
|
|
use ReflectionClass;
|
|
use ReflectionFunction;
|
|
use ReflectionMethod;
|
|
|
|
/**
|
|
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
|
*
|
|
* les types de fonctions supportés sont:
|
|
* - fonctions simples (globales ou dans un namespace)
|
|
* - classes (l'appel de cette "fonction" provoque l'instanciation de la classe)
|
|
* - méthodes statiques (liées à une classe)
|
|
* - méthodes non statiques (liées à un objet)
|
|
* - Closure
|
|
*
|
|
* 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 {
|
|
private static function _is_invalid(?string $f): bool {
|
|
return $f === null || $f === "" || $f === "::" || $f === "->";
|
|
}
|
|
|
|
private static function _is_nfunction(?string $f): bool {
|
|
return strpos($f, "\\") !== false;
|
|
}
|
|
|
|
private static function _parse_static(?string &$m): bool {
|
|
$pos = strpos($m, "::");
|
|
if ($pos === false) return false;
|
|
$m = substr($m, $pos + 2);
|
|
return true;
|
|
}
|
|
|
|
private static function _parse_method(?string &$m): bool {
|
|
$pos = strpos($m, "->");
|
|
if ($pos === false) return false;
|
|
$m = substr($m, $pos + 2);
|
|
return true;
|
|
}
|
|
|
|
#############################################################################
|
|
# Fonctions
|
|
|
|
/**
|
|
* 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 function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
|
if ($strict) {
|
|
$msg = var_export($func, true);
|
|
$reason = null;
|
|
}
|
|
if ($func instanceof ReflectionFunction) return true;
|
|
if (is_string($func)) {
|
|
$c = false;
|
|
$f = $func;
|
|
} elseif (is_array($func)) {
|
|
if (!array_key_exists(0, $func)) return false;
|
|
$c = $func[0];
|
|
if (!array_key_exists(1, $func)) return false;
|
|
$f = $func[1];
|
|
} else {
|
|
return false;
|
|
}
|
|
if ($c !== false) return false;
|
|
if (!is_string($f)) return false;
|
|
if (self::_is_invalid($f)) return false;
|
|
if (self::_parse_static($f)) return false;
|
|
if (self::_parse_method($f)) return false;
|
|
if ($strict) {
|
|
$reason = null;
|
|
if (class_exists($f)) {
|
|
$reason = "$msg: is a class";
|
|
return false;
|
|
}
|
|
if (!function_exists($f)) {
|
|
$reason = "$msg: function not found";
|
|
return false;
|
|
}
|
|
}
|
|
$func = [false, $f];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une fonction simple avec les règles de
|
|
* {@link self::verifix_function()}
|
|
*/
|
|
static function is_function($func, bool $strict=true, ?string &$reason=null): bool {
|
|
return self::verifix_function($func, $strict, $reason);
|
|
}
|
|
|
|
#############################################################################
|
|
# Classes
|
|
|
|
/**
|
|
* vérifier que $func est une classe et la normaliser le cas échéant.
|
|
* retourner true si c'est une classe, false sinon
|
|
*
|
|
* les formes suivantes sont supportées:
|
|
* - "class"
|
|
* - ["class", false, ...$args] c'est la forme normalisée
|
|
*
|
|
* @param bool $strict vérifier l'existence de la classe (ne pas uniquement
|
|
* faire une vérification syntaxique)
|
|
*/
|
|
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
|
if ($strict) {
|
|
$msg = var_export($func, true);
|
|
$reason = null;
|
|
}
|
|
if ($func instanceof ReflectionClass) return true;
|
|
if (is_string($func)) {
|
|
$c = $func;
|
|
$f = false;
|
|
} elseif (is_array($func)) {
|
|
if (!array_key_exists(0, $func)) return false;
|
|
$c = $func[0];
|
|
if (!array_key_exists(1, $func)) return false;
|
|
$f = $func[1];
|
|
} else {
|
|
return false;
|
|
}
|
|
if (!is_string($c)) return false;
|
|
if (self::_is_invalid($c)) return false;
|
|
if (self::_parse_static($c)) return false;
|
|
if (self::_parse_method($c)) return false;
|
|
if ($f !== false) return false;
|
|
if ($strict) {
|
|
if (!class_exists($c)) {
|
|
$reason = "$msg: class not found";
|
|
return false;
|
|
}
|
|
}
|
|
$func = [$c, false];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une classe avec les règles de
|
|
* {@link self::verifix_class()}
|
|
*/
|
|
static function is_class($func, bool $strict=true, ?string &$reason=null): bool {
|
|
return self::verifix_class($func, $strict, $reason);
|
|
}
|
|
|
|
#############################################################################
|
|
# Méthodes statiques
|
|
|
|
private static function _parse_class_s(?string $cs, ?string &$c, ?string &$s): bool {
|
|
if (self::_is_invalid($cs) || self::_parse_method($cs)) return false;
|
|
$pos = strpos($cs, "::");
|
|
if ($pos === false) return false;
|
|
if ($pos === 0) return false;
|
|
$tmpc = substr($cs, 0, $pos);
|
|
$cs = substr($cs, $pos + 2);
|
|
if (self::_is_nfunction($cs)) return false;
|
|
[$c, $s] = [$tmpc, cv::vn($cs)];
|
|
return true;
|
|
}
|
|
|
|
private static function _parse_c_static(?string $cs, ?string &$c, ?string &$s, ?bool &$bound): bool {
|
|
if (self::_is_invalid($cs) || self::_parse_method($cs)) return false;
|
|
$pos = strpos($cs, "::");
|
|
if ($pos === false) return false;
|
|
if ($pos == strlen($cs) - 2) return false;
|
|
if ($pos > 0) {
|
|
$tmpc = substr($cs, 0, $pos);
|
|
$bound = true;
|
|
} else {
|
|
$tmpc = null;
|
|
$bound = false;
|
|
}
|
|
$cs = substr($cs, $pos + 2);
|
|
if (self::_is_nfunction($cs)) return false;
|
|
[$c, $s] = [$tmpc, cv::vn($cs)];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une méthode statique, et la normaliser le cas
|
|
* échéant. retourner true si c'est une méthode statique, false sinon
|
|
*
|
|
* les formes suivantes sont supportées (XXX étant null ou n'importe quelle
|
|
* valeur scalaire de n'importe quel type sauf false)
|
|
* - "XXX::function"
|
|
* - ["XXX::function", ...$args]
|
|
* - [XXX, "::function", ...$args]
|
|
* - [XXX, "function", ...$args] c'est la forme normalisée
|
|
*
|
|
* Si XXX est une classe, la méthode statique est liée. sinon, elle doit être
|
|
* liée à une classe avant d'être utilisée
|
|
*
|
|
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
|
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
|
*/
|
|
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
|
if ($strict) {
|
|
$msg = var_export($func, true);
|
|
$reason = null;
|
|
}
|
|
if ($func instanceof ReflectionMethod) {
|
|
$bound = false;
|
|
return true;
|
|
}
|
|
if (is_string($func)) {
|
|
if (!self::_parse_c_static($func, $c, $f, $bound)) return false;
|
|
$cf = [$c, $f];
|
|
} elseif (is_array($func)) {
|
|
$cf = $func;
|
|
if (!array_key_exists(0, $cf)) return false;
|
|
$c = $cf[0];
|
|
if ($c === false) return false;
|
|
if (is_object($c)) $c = get_class($c);
|
|
if (is_string($c)) {
|
|
if (self::_is_invalid($c)) return false;
|
|
if (self::_parse_class_s($c, $c, $f)) {
|
|
$cf[0] = $c;
|
|
if ($f !== null) {
|
|
# ["class::method"] --> ["class", "method"]
|
|
array_splice($cf, 1, 0, [$f]);
|
|
}
|
|
$bound = true;
|
|
} elseif (self::_parse_c_static($c, $c, $f, $bound)) {
|
|
# ["::method"] --> [null, "method"]
|
|
array_splice($cf, 0, 0, [null]);
|
|
$cf[1] = $f;
|
|
} else {
|
|
$cf[0] = $c;
|
|
$bound = is_string($c);
|
|
}
|
|
} else {
|
|
$cf[0] = null;
|
|
$bound = false;
|
|
}
|
|
#
|
|
if (!array_key_exists(1, $cf)) return false;
|
|
$f = $cf[1];
|
|
if (!is_string($f)) return false;
|
|
if (self::_parse_c_static($f, $rc, $f, $rbound)) {
|
|
if ($rc !== null && $c === null) {
|
|
$c = $rc;
|
|
$bound = $rbound;
|
|
}
|
|
} else {
|
|
if (self::_is_invalid($f)) return false;
|
|
if (self::_is_nfunction($f)) return false;
|
|
if (self::_parse_method($f)) return false;
|
|
self::_parse_static($f);
|
|
}
|
|
$cf[1] = $f;
|
|
} else {
|
|
return false;
|
|
}
|
|
if ($strict) {
|
|
$reason = null;
|
|
if ($bound) {
|
|
if (!class_exists($c)) {
|
|
$reason = "$msg: class not found";
|
|
return false;
|
|
}
|
|
if (!method_exists($c, $f)) {
|
|
$reason = "$msg: method not found";
|
|
return false;
|
|
}
|
|
} else {
|
|
$reason = "$msg: not bound";
|
|
}
|
|
}
|
|
$func = $cf;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une méthode statique avec les règles de
|
|
* {@link self::verifix_static()}
|
|
*/
|
|
static function is_static($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
|
return self::verifix_static($func, $strict, $bound, $reason);
|
|
}
|
|
|
|
#############################################################################
|
|
# Méthodes non statiques
|
|
|
|
private static function _parse_class_m(?string $cm, ?string &$c, ?string &$m): bool {
|
|
if (self::_is_invalid($cm) || self::_parse_static($cm)) return false;
|
|
$pos = strpos($cm, "->");
|
|
if ($pos === false) return false;
|
|
if ($pos === 0) return false;
|
|
$tmpc = substr($cm, 0, $pos);
|
|
$cm = substr($cm, $pos + 2);
|
|
if (self::_is_nfunction($cm)) return false;
|
|
[$c, $m] = [$tmpc, cv::vn($cm)];
|
|
return true;
|
|
}
|
|
|
|
private static function _parse_c_method(?string $cm, ?string &$c, ?string &$m, ?bool &$bound): bool {
|
|
if (self::_is_invalid($cm) || self::_parse_static($cm)) return false;
|
|
$pos = strpos($cm, "->");
|
|
if ($pos === false) return false;
|
|
if ($pos == strlen($cm) - 2) return false;
|
|
if ($pos > 0) {
|
|
$tmpc = substr($cm, 0, $pos);
|
|
$bound = true;
|
|
} else {
|
|
$tmpc = null;
|
|
$bound = false;
|
|
}
|
|
$cm = substr($cm, $pos + 2);
|
|
if (self::_is_nfunction($cm)) return false;
|
|
[$c, $m] = [$tmpc, cv::vn($cm)];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une méthode non statique, et la normaliser le cas
|
|
* échéant. retourner true si c'est une méthode non statique, false sinon
|
|
*
|
|
* les formes suivantes sont supportées (XXX étant null ou n'importe quelle
|
|
* valeur scalaire de n'importe quel type sauf false)
|
|
* - "XXX->function"
|
|
* - ["XXX->function", ...$args]
|
|
* - [XXX, "->function", ...$args]
|
|
* - [XXX, "function", ...$args] c'est la forme normalisée
|
|
*
|
|
* Si XXX est une classe, la méthode est liée. sinon, elle doit être liée à un
|
|
* objet avant d'être utilisée
|
|
*
|
|
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
|
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
|
*/
|
|
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
|
if ($strict) {
|
|
$msg = var_export($func, true);
|
|
$reason = null;
|
|
}
|
|
if ($func instanceof ReflectionMethod) {
|
|
$bound = false;
|
|
return true;
|
|
}
|
|
if (is_string($func)) {
|
|
if (!self::_parse_c_method($func, $c, $f, $bound)) return false;
|
|
$cf = [$c, $f];
|
|
} elseif (is_array($func)) {
|
|
$cf = $func;
|
|
if (!array_key_exists(0, $cf)) return false;
|
|
$c = $cf[0];
|
|
if ($c === false) return false;
|
|
if (is_object($c)) {
|
|
$bound = true;
|
|
} elseif (is_string($c)) {
|
|
if (self::_is_invalid($c)) return false;
|
|
if (self::_parse_class_m($c, $c, $f)) {
|
|
$cf[0] = $c;
|
|
if ($f !== null) {
|
|
# ["class->method"] --> ["class", "method"]
|
|
array_splice($cf, 1, 0, [$f]);
|
|
}
|
|
$bound = true;
|
|
} elseif (self::_parse_c_method($c, $c, $f, $bound)) {
|
|
# ["->method"] --> [null, "method"]
|
|
array_splice($cf, 0, 0, [null]);
|
|
$cf[1] = $f;
|
|
} else {
|
|
$cf[0] = $c;
|
|
$bound = is_string($c);
|
|
}
|
|
} else {
|
|
$cf[0] = null;
|
|
$bound = false;
|
|
}
|
|
#
|
|
if (!array_key_exists(1, $cf)) return false;
|
|
$f = $cf[1];
|
|
if (!is_string($f)) return false;
|
|
if (self::_parse_c_method($f, $rc, $f, $rbound)) {
|
|
if ($rc !== null && $c === null) {
|
|
$c = $rc;
|
|
$bound = $rbound;
|
|
}
|
|
} else {
|
|
if (self::_is_invalid($f)) return false;
|
|
if (self::_is_nfunction($f)) return false;
|
|
if (self::_parse_static($f)) return false;
|
|
self::_parse_method($f);
|
|
}
|
|
$cf[1] = $f;
|
|
} else {
|
|
return false;
|
|
}
|
|
if ($strict) {
|
|
$reason = null;
|
|
if ($bound) {
|
|
if (!is_object($c) && !class_exists($c)) {
|
|
$reason = "$msg: class not found";
|
|
return false;
|
|
}
|
|
if (!method_exists($c, $f)) {
|
|
$reason = "$msg: method not found";
|
|
return false;
|
|
}
|
|
} else {
|
|
$reason = "$msg: not bound";
|
|
}
|
|
}
|
|
$func = $cf;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* vérifier que $func est une méthode non statique avec les règles de
|
|
* {@link self::verifix_method()}
|
|
*/
|
|
static function is_method($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
|
return self::verifix_method($func, $strict, $bound, $reason);
|
|
}
|
|
|
|
#############################################################################
|
|
# func
|
|
|
|
static function with($func, bool $strict=true): self {
|
|
if ($func instanceof Closure) {
|
|
return new self($func, false, null);
|
|
} elseif (self::verifix_function($func, $strict, $reason)) {
|
|
return new self($func, false, $reason);
|
|
} elseif (self::verifix_class($func, $strict, $reason)) {
|
|
return new self($func, false, $reason);
|
|
} else {
|
|
$valid = true;
|
|
if (is_array($func) && array_key_exists(0, $func) && is_object($func[0])) {
|
|
if (self::verifix_method($func, $strict, $bound, $reason)) {
|
|
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
|
} else {
|
|
$valid = false;
|
|
}
|
|
} else {
|
|
if (self::verifix_static($func, $strict, $bound, $reason)) {
|
|
} elseif (self::verifix_method($func, $strict, $bound, $reason)) {
|
|
} else {
|
|
$valid = false;
|
|
}
|
|
}
|
|
if ($valid) return new self($func, $bound, $reason);
|
|
}
|
|
if ($reason === null) {
|
|
$msg = var_export($func, true);
|
|
$reason = "$msg: not a callable";
|
|
}
|
|
throw new ValueException($reason);
|
|
}
|
|
|
|
static function call($func, ...$args) {
|
|
return self::with($func)->invoke($args);
|
|
}
|
|
|
|
#############################################################################
|
|
|
|
const TYPE_CLOSURE = 0, TYPE_SIMPLE = 1, TYPE_CLASS = 2, TYPE_STATIC = 3, TYPE_METHOD = 4;
|
|
|
|
protected function __construct($func, bool $bound=false, ?string $reason=null) {
|
|
$object = null;
|
|
$prefixArgs = [];
|
|
if ($func instanceof Closure) {
|
|
$type = self::TYPE_CLOSURE;
|
|
$object = $func;
|
|
$reflection = new ReflectionFunction($func);
|
|
} elseif ($func instanceof ReflectionFunction) {
|
|
$type = self::TYPE_SIMPLE;
|
|
$reflection = $func;
|
|
} elseif ($func instanceof ReflectionClass) {
|
|
$type = self::TYPE_CLASS;
|
|
$reflection = $func;
|
|
} elseif ($func instanceof ReflectionMethod) {
|
|
$type = self::TYPE_STATIC;
|
|
$reflection = $func;
|
|
} elseif (is_array($func)) {
|
|
if (count($func) > 2) {
|
|
$prefixArgs = array_slice($func, 2);
|
|
$func = array_slice($func, 0, 2);
|
|
}
|
|
[$c, $f] = $func;
|
|
if ($c === false) {
|
|
# fonction simple
|
|
$type = self::TYPE_SIMPLE;
|
|
$reflection = new ReflectionFunction($f);
|
|
} elseif ($f === false) {
|
|
# classe
|
|
$type = self::TYPE_CLASS;
|
|
$reflection = new ReflectionClass($c);
|
|
} elseif ($c !== null) {
|
|
# methode
|
|
$reflection = new ReflectionMethod($c, $f);
|
|
if (is_object($c)) {
|
|
$type = self::TYPE_METHOD;
|
|
$object = $c;
|
|
} else {
|
|
$type = self::TYPE_STATIC;
|
|
}
|
|
} else {
|
|
throw new StateException("invalid func");
|
|
}
|
|
} else {
|
|
throw new StateException("invalid func");
|
|
}
|
|
if ($reflection instanceof ReflectionClass) {
|
|
$constructor = $reflection->getConstructor();
|
|
if ($constructor === null) {
|
|
$variadic = false;
|
|
$minArgs = $maxArgs = 0;
|
|
} else {
|
|
$variadic = $constructor->isVariadic();
|
|
$minArgs = $constructor->getNumberOfRequiredParameters();
|
|
$maxArgs = $constructor->getNumberOfParameters();
|
|
}
|
|
} else {
|
|
$variadic = $reflection->isVariadic();
|
|
$minArgs = $reflection->getNumberOfRequiredParameters();
|
|
$maxArgs = $reflection->getNumberOfParameters();
|
|
}
|
|
|
|
$this->type = $type;
|
|
$this->reflection = $reflection;
|
|
$this->variadic = $variadic;
|
|
$this->minArgs = $minArgs;
|
|
$this->maxArgs = $maxArgs;
|
|
$this->object = $object;
|
|
$this->prefixArgs = $prefixArgs;
|
|
}
|
|
|
|
protected int $type;
|
|
|
|
/** @var ReflectionFunction|ReflectionMethod|ReflectionClass */
|
|
protected $reflection;
|
|
|
|
protected bool $variadic;
|
|
|
|
protected int $minArgs;
|
|
|
|
protected int $maxArgs;
|
|
|
|
protected ?object $object;
|
|
|
|
protected array $prefixArgs;
|
|
|
|
function bind($objectOrClass, bool $rebind=false): self {
|
|
|
|
}
|
|
|
|
function bindClass($objectOrClass, bool $rebind=false): self {
|
|
|
|
}
|
|
|
|
function invoke(?array $args=null) {
|
|
$args = array_merge($this->prefixArgs, $args ?? []);
|
|
if (!$this->variadic) $args = array_slice($args, 0, $this->maxArgs);
|
|
$minArgs = $this->minArgs;
|
|
while (count($args) < $minArgs) $args[] = null;
|
|
|
|
switch ($this->type) {
|
|
case self::TYPE_CLOSURE:
|
|
/** @var Closure $closure */
|
|
$closure = $this->object;
|
|
return $closure(...$args);
|
|
case self::TYPE_SIMPLE:
|
|
/** @var ReflectionFunction $function */
|
|
$function = $this->reflection;
|
|
return $function->invoke(...$args);
|
|
case self::TYPE_STATIC:
|
|
case self::TYPE_METHOD:
|
|
/** @var ReflectionMethod $method */
|
|
$method = $this->reflection;
|
|
return $method->invoke($this->object, ...$args);
|
|
case self::TYPE_CLASS:
|
|
/** @var ReflectionClass $class */
|
|
$class = $this->reflection;
|
|
return $class->newInstance(...$args);
|
|
}
|
|
}
|
|
}
|