Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Zend/tests/get_error_handler.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class C {
static function handleStatic() {}
}

class Invokable {
class InvokableHandler {
public function __invoke() {
}
}
Expand Down Expand Up @@ -46,8 +46,8 @@ echo "\nClosure\n";
set_error_handler($f = function () {});
var_dump(get_error_handler() === $f);

echo "\nInvokable\n";
set_error_handler($object = new Invokable());
echo "\nInvokableHandler\n";
set_error_handler($object = new InvokableHandler());
var_dump(get_error_handler() === $object);

echo "\nStable return value\n";
Expand Down Expand Up @@ -80,7 +80,7 @@ bool(true)
Closure
bool(true)

Invokable
InvokableHandler
bool(true)

Stable return value
Expand Down
8 changes: 4 additions & 4 deletions Zend/tests/get_exception_handler.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class C {
static function handleStatic() {}
}

class Invokable {
class InvokableHandler {
public function __invoke() {
}
}
Expand Down Expand Up @@ -46,8 +46,8 @@ echo "\nClosure\n";
set_exception_handler($f = function () {});
var_dump(get_exception_handler() === $f);

echo "\nInvokable\n";
set_exception_handler($object = new Invokable());
echo "\nInvokableHandler\n";
set_exception_handler($object = new InvokableHandler());
var_dump(get_exception_handler() === $object);

echo "\nStable return value\n";
Expand Down Expand Up @@ -79,7 +79,7 @@ bool(true)
Closure
bool(true)

Invokable
InvokableHandler
bool(true)

Stable return value
Expand Down
45 changes: 45 additions & 0 deletions Zend/tests/magic_methods/invokable_abstract_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
Abstract class can implement Invokable with abstract or concrete __invoke()
--FILE--
<?php

/* Abstract class with abstract __invoke forces signature on subclasses */
abstract class TypedHandler implements Invokable {
abstract public function __invoke(int $x): int;
}

class Doubler extends TypedHandler {
public function __invoke(int $x): int { return $x * 2; }
}

var_dump(new Doubler() instanceof Invokable);
var_dump((new Doubler())(5));

/* Abstract class with concrete __invoke */
abstract class BaseHandler implements Invokable {
public function __invoke(string $s): string { return strtoupper($s); }
}

class MyHandler extends BaseHandler {}

var_dump(new MyHandler() instanceof Invokable);
var_dump((new MyHandler())("hello"));

/* Abstract class implementing Invokable without __invoke at all (deferred to child) */
abstract class DeferredHandler implements Invokable {}

class ConcreteHandler extends DeferredHandler {
public function __invoke(): string { return "concrete"; }
}

var_dump(new ConcreteHandler() instanceof Invokable);
var_dump((new ConcreteHandler())());

?>
--EXPECT--
bool(true)
int(10)
bool(true)
string(5) "HELLO"
bool(true)
string(8) "concrete"
30 changes: 30 additions & 0 deletions Zend/tests/magic_methods/invokable_anonymous_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
Anonymous class with __invoke() auto-implements Invokable
--FILE--
<?php

$obj = new class {
public function __invoke(): string {
return "anonymous";
}
};

var_dump($obj instanceof Invokable);
var_dump($obj());

/* Anonymous class with explicit implements */
$obj2 = new class implements Invokable {
public function __invoke(): int {
return 42;
}
};

var_dump($obj2 instanceof Invokable);
var_dump($obj2());

?>
--EXPECT--
bool(true)
string(9) "anonymous"
bool(true)
int(42)
75 changes: 75 additions & 0 deletions Zend/tests/magic_methods/invokable_automatic_implementation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
--TEST--
Invokable is automatically implemented
--FILE--
<?php

/* Basic auto-implementation */
class Test {
public function __invoke(): string {
return "foo";
}
}

var_dump(new Test instanceof Invokable);
var_dump((new ReflectionClass(Test::class))->getInterfaceNames());

/* Inheritance: child inherits Invokable from parent */
class Child extends Test {}

var_dump(new Child instanceof Invokable);
var_dump((new ReflectionClass(Child::class))->getInterfaceNames());

/* Child overrides __invoke */
class ChildOverride extends Test {
public function __invoke(): string {
return "bar";
}
}

var_dump(new ChildOverride instanceof Invokable);

/* Arbitrary signature: different params and return type */
class Adder {
public function __invoke(int $a, int $b): int {
return $a + $b;
}
}

var_dump(new Adder instanceof Invokable);

/* No params, no return type */
class NoSig {
public function __invoke() {}
}

var_dump(new NoSig instanceof Invokable);

/* Explicit + implicit: class has __invoke and writes implements Invokable */
class ExplicitAndImplicit implements Invokable {
public function __invoke(): void {}
}

var_dump(new ExplicitAndImplicit instanceof Invokable);
/* Should appear only once in the interface list */
var_dump((new ReflectionClass(ExplicitAndImplicit::class))->getInterfaceNames());

?>
--EXPECT--
bool(true)
array(1) {
[0]=>
string(9) "Invokable"
}
bool(true)
array(1) {
[0]=>
string(9) "Invokable"
}
bool(true)
bool(true)
bool(true)
bool(true)
array(1) {
[0]=>
string(9) "Invokable"
}
65 changes: 65 additions & 0 deletions Zend/tests/magic_methods/invokable_callable_covariance.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
Invokable is covariant to callable in return types
--FILE--
<?php

class Base {
public function getHandler(): callable {
return fn() => 1;
}
}

/* Invokable is a valid covariant return type for callable */
class Child extends Base {
public function getHandler(): Invokable {
return new class {
public function __invoke(): int {
return 2;
}
};
}
}

$c = new Child();
$handler = $c->getHandler();
var_dump($handler instanceof Invokable);
var_dump($handler());

/* Concrete class with __invoke is also covariant to callable */
class Handler {
public function __invoke(): int {
return 3;
}
}

class Child2 extends Base {
public function getHandler(): Handler {
return new Handler();
}
}

$c2 = new Child2();
$handler2 = $c2->getHandler();
var_dump($handler2 instanceof Invokable);
var_dump($handler2());

/* Closure is covariant to callable via Invokable */
class Child3 extends Base {
public function getHandler(): Closure {
return fn() => 4;
}
}

$c3 = new Child3();
$handler3 = $c3->getHandler();
var_dump($handler3 instanceof Invokable);
var_dump($handler3());

?>
--EXPECT--
bool(true)
int(2)
bool(true)
int(3)
bool(true)
int(4)
31 changes: 31 additions & 0 deletions Zend/tests/magic_methods/invokable_closure.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Closure implements Invokable
--FILE--
<?php

/* Arrow function */
$fn = fn() => 1;
var_dump($fn instanceof Invokable);

/* Anonymous function */
$fn2 = function(int $x): int { return $x * 2; };
var_dump($fn2 instanceof Invokable);

/* Closure::fromCallable */
$fn3 = Closure::fromCallable('strlen');
var_dump($fn3 instanceof Invokable);

/* First-class callable syntax */
$fn4 = strlen(...);
var_dump($fn4 instanceof Invokable);

/* Reflection confirms Closure implements Invokable */
var_dump(in_array('Invokable', (new ReflectionClass(Closure::class))->getInterfaceNames()));

?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
31 changes: 31 additions & 0 deletions Zend/tests/magic_methods/invokable_enum.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Enums with __invoke() auto-implement Invokable
--FILE--
<?php

/* Enum with __invoke auto-implements Invokable */
enum Color {
case Red;
case Blue;
public function __invoke(): string { return $this->name; }
}

var_dump(Color::Red instanceof Invokable);
var_dump((Color::Red)());

/* Enum explicitly implementing Invokable */
enum Direction implements Invokable {
case Up;
case Down;
public function __invoke(): int { return $this === self::Up ? 1 : -1; }
}

var_dump(Direction::Up instanceof Invokable);
var_dump((Direction::Up)());

?>
--EXPECT--
bool(true)
string(3) "Red"
bool(true)
int(1)
12 changes: 12 additions & 0 deletions Zend/tests/magic_methods/invokable_enum_without_invoke.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Enum explicitly implementing Invokable without __invoke() causes fatal error
--FILE--
<?php

enum Color implements Invokable {
case Red;
}

?>
--EXPECTF--
Fatal error: Enum Color must have an __invoke() method to implement Invokable in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/magic_methods/invokable_explicit_without_invoke.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Explicit implements Invokable without __invoke() causes fatal error
--FILE--
<?php

class Bad implements Invokable {}

?>
--EXPECTF--
Fatal error: Class Bad must have an __invoke() method to implement Invokable in %s on line %d
27 changes: 27 additions & 0 deletions Zend/tests/magic_methods/invokable_interface_extends.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Interface extending Invokable
--FILE--
<?php

interface MyInvokable extends Invokable {}

/* Class implementing MyInvokable must have __invoke */
class Good implements MyInvokable {
public function __invoke(): void {}
}

var_dump(new Good() instanceof Invokable);
var_dump(new Good() instanceof MyInvokable);

var_dump((new ReflectionClass(Good::class))->getInterfaceNames());

?>
--EXPECT--
bool(true)
bool(true)
array(2) {
[0]=>
string(11) "MyInvokable"
[1]=>
string(9) "Invokable"
}
Loading
Loading