488 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			488 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\sery\wip\php;
 | 
						|
 | 
						|
use Closure;
 | 
						|
use nur\config\Ref;
 | 
						|
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 fonctions supportées sont:
 | 
						|
 * - fonctions simples (globales ou dans un namespace)
 | 
						|
 * - fonctions statiques
 | 
						|
 * - méthodes
 | 
						|
 * - Closure
 | 
						|
 * - constructeur (l'appel de cette "fonction" provoque l'instanciation d'un
 | 
						|
 * objet)
 | 
						|
 *
 | 
						|
 * 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 {
 | 
						|
  /**
 | 
						|
   * 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_simple(&$func, bool $strict=true): bool {
 | 
						|
    if (is_string($func)) {
 | 
						|
      $func = [false, $func];
 | 
						|
    } elseif (is_array($func)) {
 | 
						|
      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 {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    $f = $func[1];
 | 
						|
    if ($f === "") return false;
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * vérifier que $func est une fonction simple avec les règles de
 | 
						|
   * {@link self::verifix_simple()}
 | 
						|
   */
 | 
						|
  static function is_simple($func, bool $strict=true): bool {
 | 
						|
    return self::verifix_simple($func, $strict);
 | 
						|
  }
 | 
						|
 | 
						|
  private static function _is_invalid(?string $m): bool {
 | 
						|
    return $m === null || $m === "" || $m === "::" || $m === "->";
 | 
						|
  }
 | 
						|
 | 
						|
  private static function _is_nfunction(?string $m): bool {
 | 
						|
    return strpos($m, "\\") !== 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;
 | 
						|
  }
 | 
						|
 | 
						|
  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): bool {
 | 
						|
    if (is_string($func)) {
 | 
						|
      if (!self::_parse_c_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 ($c === false) return false;
 | 
						|
      if (self::_parse_class_s($c, $c, $f)) {
 | 
						|
        $func[0] = $c;
 | 
						|
        if ($f !== null) {
 | 
						|
          # ["class::method"] --> ["class", "method"]
 | 
						|
          array_splice($func, 1, 0, [$f]);
 | 
						|
        }
 | 
						|
        $bound = true;
 | 
						|
      } elseif (self::_parse_c_static($c, $c, $f, $bound)) {
 | 
						|
          # ["::method"] --> [null, "method"]
 | 
						|
          array_splice($func, 0, 0, [null]);
 | 
						|
          $func[1] = $f;
 | 
						|
      } else {
 | 
						|
        if (self::_is_invalid($c)) $c = null;
 | 
						|
        $func[0] = $c;
 | 
						|
        $bound = is_string($c);
 | 
						|
      }
 | 
						|
      #
 | 
						|
      if (!array_key_exists(1, $func)) return false;
 | 
						|
      $f = $func[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;
 | 
						|
        }
 | 
						|
      } elseif (self::_is_invalid($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_is_nfunction($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_parse_method($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_parse_static($f)) {
 | 
						|
      }
 | 
						|
      $func[1] = $f;
 | 
						|
    } else {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    if ($strict && $bound && !method_exists($c, $f)) return false;
 | 
						|
    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): bool {
 | 
						|
    return self::verifix_static($func, $strict, $bound);
 | 
						|
  }
 | 
						|
 | 
						|
  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): bool {
 | 
						|
    if (is_string($func)) {
 | 
						|
      if (!self::_parse_c_method($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 ($c === false) return false;
 | 
						|
      if (self::_parse_class_m($c, $c, $f)) {
 | 
						|
        $func[0] = $c;
 | 
						|
        if ($f !== null) {
 | 
						|
          # ["class->method"] --> ["class", "method"]
 | 
						|
          array_splice($func, 1, 0, [$f]);
 | 
						|
        }
 | 
						|
        $bound = true;
 | 
						|
      } elseif (self::_parse_c_method($c, $c, $f, $bound)) {
 | 
						|
        # ["->method"] --> [null, "method"]
 | 
						|
        array_splice($func, 0, 0, [null]);
 | 
						|
        $func[1] = $f;
 | 
						|
      } else {
 | 
						|
        if (self::_is_invalid($c)) $c = null;
 | 
						|
        $func[0] = $c;
 | 
						|
        $bound = is_string($c);
 | 
						|
      }
 | 
						|
      #
 | 
						|
      if (!array_key_exists(1, $func)) return false;
 | 
						|
      $f = $func[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;
 | 
						|
        }
 | 
						|
      } elseif (self::_is_invalid($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_is_nfunction($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_parse_static($f)) {
 | 
						|
        return false;
 | 
						|
      } elseif (self::_parse_method($f)) {
 | 
						|
      }
 | 
						|
      $func[1] = $f;
 | 
						|
    } else {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    if ($strict && $bound && !method_exists($c, $f)) return false;
 | 
						|
    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): bool {
 | 
						|
    return self::verifix_method($func, $strict, $bound);
 | 
						|
  }
 | 
						|
  
 | 
						|
  static function verifix_cons(&$func, bool $strict=true): bool {
 | 
						|
    if (is_string($func)) {
 | 
						|
      
 | 
						|
    } elseif (is_array($func)) {
 | 
						|
      
 | 
						|
    } else {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return false;#XXX
 | 
						|
  }
 | 
						|
 | 
						|
  static function with($func, bool $strict=true): self {
 | 
						|
    if ($func instanceof Closure) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_simple($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_cons($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } else {
 | 
						|
      if (self::verifix_static($func, $strict, $bound)) {
 | 
						|
      } elseif (self::verifix_method($func, $strict, $bound)) {
 | 
						|
      } else {
 | 
						|
        throw ValueException::invalid_type($func, "callable");
 | 
						|
      }
 | 
						|
      if (!$bound) throw ValueException::invalid_value($func, null, "is not bound");
 | 
						|
      return new self($func);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static function call($func, ...$args) {
 | 
						|
    return self::with($func)->invoke($args);
 | 
						|
  }
 | 
						|
 | 
						|
  static function with_object($func, object $object, bool $rebind=false, bool $strict=true): self {
 | 
						|
    if ($func instanceof Closure) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_simple($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_cons($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } else {
 | 
						|
        if (self::verifix_method($func, $strict, $bound)) {
 | 
						|
        } elseif (self::verifix_static($func, $strict, $bound)) {
 | 
						|
        } else {
 | 
						|
          throw ValueException::invalid_type($func, "callable");
 | 
						|
        }
 | 
						|
      if (!$bound || $rebind) $func[0] = $object;
 | 
						|
      return new self($func);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static function with_class($func, $class, bool $rebind=false, bool $strict=true): self {
 | 
						|
    if ($func instanceof Closure) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_simple($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } elseif (self::verifix_cons($func, $strict)) {
 | 
						|
      return new self($func);
 | 
						|
    } else {
 | 
						|
      if (self::verifix_method($func, $strict, $bound)) {
 | 
						|
      } elseif (self::verifix_static($func, $strict, $bound)) {
 | 
						|
      } else {
 | 
						|
        throw ValueException::invalid_type($func, "callable");
 | 
						|
      }
 | 
						|
      if (!$bound || $rebind) {
 | 
						|
        if (is_object($class)) $class = get_class($class);
 | 
						|
        $func[0] = $class;
 | 
						|
      }
 | 
						|
      return new self($func);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  #############################################################################
 | 
						|
 | 
						|
  const TYPE_CLOSURE = 0, TYPE_SIMPLE = 1, TYPE_STATIC = 2, TYPE_METHOD = 3, TYPE_CLASS = 4;
 | 
						|
 | 
						|
  function __construct($func) {
 | 
						|
    $object = null;
 | 
						|
    $prefixArgs = [];
 | 
						|
    if ($func instanceof Closure) {
 | 
						|
      $type = self::TYPE_CLOSURE;
 | 
						|
      $reflection = new ReflectionFunction($func);
 | 
						|
      $object = $func;
 | 
						|
    } elseif ($func instanceof ReflectionFunction) {
 | 
						|
      $type = self::TYPE_SIMPLE;
 | 
						|
      $reflection = $func;
 | 
						|
    } elseif ($func instanceof ReflectionMethod) {
 | 
						|
      $type = self::TYPE_STATIC;
 | 
						|
      $reflection = $func;
 | 
						|
    } elseif ($func instanceof ReflectionClass) {
 | 
						|
      $type = self::TYPE_CLASS;
 | 
						|
      $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($func);
 | 
						|
        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 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:
 | 
						|
    case self::TYPE_STATIC:
 | 
						|
      /** @var ReflectionFunction $function */
 | 
						|
      $function = $this->reflection;
 | 
						|
      return $function->invoke(...$args);
 | 
						|
    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);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |