diff --git a/src/php/func.php b/src/php/func.php index 126c909..2c45e4e 100644 --- a/src/php/func.php +++ b/src/php/func.php @@ -2,6 +2,8 @@ namespace nur\sery\php; use Closure; +use Exception; +use nur\sery\A; use nur\sery\cv; use nur\sery\StateException; use nur\sery\ValueException; @@ -428,8 +430,14 @@ class func { ############################################################################# # func + const TYPE_MASK = 0b11; + + const FLAG_STATIC = 0b100; + const TYPE_CLOSURE = 0, TYPE_FUNCTION = 1, TYPE_CLASS = 2, TYPE_METHOD = 3; + const TYPE_STATIC = self::TYPE_METHOD | self::FLAG_STATIC; + protected static function not_a_callable($func, ?string $reason) { if ($reason === null) { $msg = var_export($func, true); @@ -438,53 +446,56 @@ class func { return new ValueException($reason); } - static function with($func, bool $strict=true): self { + static function with($func, ?array $args=null, bool $strict=true): self { if (!is_array($func)) { if ($func instanceof Closure) { - return new self(self::TYPE_CLOSURE, $func); + return new self(self::TYPE_CLOSURE, $func, $args); } elseif ($func instanceof ReflectionFunction) { - return new self(self::TYPE_FUNCTION, $func); + return new self(self::TYPE_FUNCTION, $func, $args); } elseif ($func instanceof ReflectionClass) { - return new self(self::TYPE_CLASS, $func); + return new self(self::TYPE_CLASS, $func, $args); } elseif ($func instanceof ReflectionMethod) { - return new self(self::TYPE_METHOD, $func, false); + return new self(self::TYPE_METHOD, $func, $args, false); } } if (self::verifix_function($func, $strict, $reason)) { - return new self(self::TYPE_FUNCTION, $func, false, $reason); + return new self(self::TYPE_FUNCTION, $func, $args, false, $reason); } elseif (self::verifix_class($func, $strict, $reason)) { - return new self(self::TYPE_CLASS, $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)) { - $type = self::TYPE_METHOD; - } elseif (self::verifix_static($func, $strict, $bound, $reason)) { - $type = self::TYPE_METHOD; - } else { - $valid = false; - } - } else { - if (self::verifix_static($func, $strict, $bound, $reason)) { - $type = self::TYPE_METHOD; - } elseif (self::verifix_method($func, $strict, $bound, $reason)) { - $type = self::TYPE_METHOD; - } else { - $valid = false; - } - } - if ($valid) return new self($type, $func, $bound, $reason); + return new self(self::TYPE_CLASS, $func, $args, false, $reason); + } elseif (self::verifix_method($func, $strict, $bound, $reason)) { + return new self(self::TYPE_METHOD, $func, $args, $bound, $reason); + } elseif (self::verifix_static($func, $strict, $bound, $reason)) { + return new self(self::TYPE_STATIC, $func, $args, $bound, $reason); } throw self::not_a_callable($func, $reason); } + static function ensure($func, ?array $args=null, bool $strict=true): self { + $func = self::with($func, $args, $strict); + if (!$func->isBound()) { + throw self::not_a_callable($func->func, $func->reason); + } + return $func; + } + + static function check($func, ?array $args=null, bool $strict=true): bool { + try { + self::ensure($func, $args, $strict); + return true; + } catch (Exception $e) { + return false; + } + } + static function call($func, ...$args) { return self::with($func)->invoke($args); } ############################################################################# - protected function __construct(int $type, $func, bool $bound=false, ?string $reason=null) { + protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) { + $flags = $type & ~self::TYPE_MASK; + $type = $type & self::TYPE_MASK; $object = null; $prefixArgs = []; if (!is_array($func)) { @@ -515,8 +526,10 @@ class func { throw StateException::unexpected_state(); } } + A::merge($prefixArgs, $args); $this->type = $type; + $this->flags = $flags; $this->func = $func; $this->bound = $bound; $this->reason = $reason; @@ -527,6 +540,8 @@ class func { protected int $type; + protected int $flags; + protected ?array $func; protected bool $bound; @@ -550,15 +565,19 @@ class func { $variadic = false; $minArgs = $maxArgs = 0; if ($reflection instanceof Closure) { + $r = new ReflectionFunction($reflection); + $variadic = $r->isVariadic(); + $minArgs = $r->getNumberOfRequiredParameters(); + $maxArgs = $r->getNumberOfParameters(); } elseif ($reflection instanceof ReflectionClass) { - $constructor = $reflection->getConstructor(); - if ($constructor === null) { + $r = $reflection->getConstructor(); + if ($r === null) { $variadic = false; $minArgs = $maxArgs = 0; } else { - $variadic = $constructor->isVariadic(); - $minArgs = $constructor->getNumberOfRequiredParameters(); - $maxArgs = $constructor->getNumberOfParameters(); + $variadic = $r->isVariadic(); + $minArgs = $r->getNumberOfRequiredParameters(); + $maxArgs = $r->getNumberOfParameters(); } } elseif ($reflection !== null) { $variadic = $reflection->isVariadic(); @@ -571,19 +590,25 @@ class func { $this->maxArgs = $maxArgs; } - function bind($object, bool $rebind=false): self { + function isBound(): bool { + if ($this->type !== self::TYPE_METHOD) return true; + if ($this->flags & self::FLAG_STATIC) return $this->bound; + else return $this->bound && $this->object !== null; + } + + function bind($object): self { if ($this->type !== self::TYPE_METHOD) return $this; - $bound = $this->bound && $this->object !== null; - if ($bound && !$rebind) return $this; + [$c, $f] = $this->func; if ($this->reflection === null) { $this->func[0] = $c = $object; $this->updateReflection(new ReflectionMethod($c, $f)); } - if (is_string($c) && is_object($object) && !($object instanceof $c)) { - throw ValueException::invalid_type($object, $c); - } - if (is_object($object)) { + if (is_object($object) && !($this->flags & self::FLAG_STATIC)) { + if (is_object($c)) $c = get_class($c); + if (is_string($c) && !($object instanceof $c)) { + throw ValueException::invalid_type($object, $c); + } $this->object = $object; $this->bound = true; } diff --git a/tests/php/funcTest.php b/tests/php/funcTest.php index 9b183bd..e15a20f 100644 --- a/tests/php/funcTest.php +++ b/tests/php/funcTest.php @@ -1007,13 +1007,13 @@ namespace nur\sery\php { $func = func::with(C1::class); /** @var C1 $i1 */ $i1 = $func->invoke(); - self::assertInstanceOf(C1::class, $i1); self::assertSame(null, $i1->first); + self::assertInstanceOf(C1::class, $i1); self::assertSame(0, $i1->base); $i1 = $func->invoke([]); - self::assertInstanceOf(C1::class, $i1); self::assertSame(null, $i1->first); + self::assertInstanceOf(C1::class, $i1); self::assertSame(0, $i1->base); $i1 = $func->invoke([1]); - self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->first); + self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->base); $i1 = $func->invoke([1, 2]); - self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->first); + self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->base); } private static function invoke_asserts(): array { @@ -1082,9 +1082,33 @@ namespace nur\sery\php { self::assertException(ReflectionException::class, $bind_ko([null, "->tmethod"], SC::class)); self::assertSame(11, $bind_ok([null, "tmethod"], $sc)); - self::assertSame(11, $bind_ok([null, "::tmethod"], $sc)); + self::assertException(ReflectionException::class, $bind_ko([null, "::tmethod"], $sc)); self::assertSame(11, $bind_ok([null, "->tmethod"], $sc)); } + + function testArgs() { + $func = function(int $a, int $b, int $c): int { + return $a + $b + $c; + }; + + self::assertSame(6, func::call($func, 1, 2, 3)); + self::assertSame(6, func::call($func, 1, 2, 3, 4)); + + self::assertSame(6, func::with($func)->invoke([1, 2, 3])); + self::assertSame(6, func::with($func, [1])->invoke([2, 3])); + self::assertSame(6, func::with($func, [1, 2])->invoke([3])); + self::assertSame(6, func::with($func, [1, 2, 3])->invoke()); + self::assertSame(6, func::with($func, [1, 2, 3, 4])->invoke()); + } + + function testRebind() { + $func = func::with([C1::class, "tmethod"]); + self::assertSame(11, $func->bind(new C1(0))->invoke()); + self::assertSame(12, $func->bind(new C1(1))->invoke()); + self::assertException(ValueException::class, function() use ($func) { + $func->bind(new C0())->invoke(); + }); + } } } @@ -1126,18 +1150,18 @@ namespace nur\sery\php\impl { } class C1 { - function __construct($first) { - $this->first = $first; + function __construct(int $base=0) { + $this->base = $base; } - public $first; + public int $base; static function tstatic(): int { return 10; } function tmethod(): int { - return 11; + return 11 + $this->base; } } }