Skip to content

Commit eba9a20

Browse files
committed
Test optional interfaces
1 parent 4ed9176 commit eba9a20

File tree

10 files changed

+283
-0
lines changed

10 files changed

+283
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Optional interfaces are autoloaded
3+
--FILE--
4+
<?php
5+
6+
spl_autoload_register(function ($class) {
7+
echo "Autoloading: $class\n";
8+
9+
if ($class === 'ExistingInterface')
10+
eval("interface ExistingInterface {}");
11+
});
12+
13+
class TestClass implements ?ExistingInterface, ?NonExistantInterface {}
14+
15+
$c = new TestClass;
16+
echo implode(',', class_implements($c))."\n";
17+
echo implode(',', class_implements('TestClass'))."\n";
18+
19+
?>
20+
--EXPECT--
21+
Autoloading: ExistingInterface
22+
Autoloading: NonExistantInterface
23+
ExistingInterface
24+
ExistingInterface
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Dynamically defined interfaces only affect classes defined later
3+
--FILE--
4+
<?php
5+
6+
class AncientClass implements ?OptionalInterface {}
7+
$ancientClass = new AncientClass;
8+
echo 'AC implements: '.implode(', ', class_implements('AncientClass')),"\n";
9+
echo 'AC object implements: '.implode(', ', class_implements($ancientClass)),"\n\n";
10+
11+
eval('interface OptionalInterface {}');
12+
echo "Interface defined\n\n";
13+
14+
class NewClass implements ?OptionalInterface {}
15+
16+
// Newly defined class implements the interface
17+
$newClass = new NewClass;
18+
echo 'NC implements: '.implode(', ', class_implements('NewClass')),"\n";
19+
echo 'NC implements: '.implode(', ', class_implements($newClass)),"\n\n";
20+
21+
// The old class doesn't
22+
echo 'AC object implements: '.implode(', ', class_implements($ancientClass)),"\n";
23+
24+
echo 'AC implements: '.implode(', ', class_implements('AncientClass')),"\n";
25+
26+
$ancientClass2 = new AncientClass;
27+
echo 'New AC object implements: '.implode(', ', class_implements($ancientClass2)),"\n";
28+
29+
?>
30+
--EXPECT--
31+
AC implements:
32+
AC object implements:
33+
34+
Interface defined
35+
36+
NC implements: OptionalInterface
37+
NC implements: OptionalInterface
38+
39+
AC object implements:
40+
AC implements:
41+
New AC object implements:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Optional interfaces are rechecked on subsequent requests
3+
--INI--
4+
opcache.enable_cli=1
5+
opcache.enable=1
6+
--EXTENSIONS--
7+
opcache
8+
--FILE--
9+
<?php
10+
11+
$cmd = PHP_BINARY . " " . escapeshellarg(__DIR__.'/opcached_script.php');
12+
$interfaceFile = __DIR__.'/opcached_interface.inc';
13+
14+
echo shell_exec($cmd);
15+
16+
file_put_contents($interfaceFile, '<?php interface OpcachedInterface {}');
17+
18+
echo shell_exec($cmd);
19+
20+
unlink($interfaceFile);
21+
22+
echo shell_exec($cmd);
23+
24+
?>
25+
--EXPECT--
26+
OpcachedInterface is not defined
27+
OpcachedClass implements
28+
29+
OpcachedInterface is defined
30+
OpcachedClass implements OpcachedInterface
31+
32+
OpcachedInterface is not defined
33+
OpcachedClass implements
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class OpcachedClass implements ?OpcachedInterface {}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--TEST--
2+
Optional interfaces work
3+
--FILE--
4+
<?php
5+
6+
interface ExistingInterface {}
7+
8+
class ImplementingOptionalInterface implements ?ExistingInterface {}
9+
class SkippingOptionalInterface implements ?NonExistantInterface {}
10+
class MultipleOptionalsExistingFirst implements ?ExistingInterface, ?NonExistantInterface {}
11+
class MultipleOptionalsExistingLast implements ?NonExistantInterface, ?ExistingInterface {}
12+
class MixedOptionalFirst implements ?NonExistantInterface, ExistingInterface {}
13+
class MixedOptionalLast implements ExistingInterface, ?NonExistantInterface {}
14+
15+
interface ExtendingExistingOptional extends ExistingInterface {}
16+
interface ExtendingNonexistantOptional extends ?NonExistantInterface {}
17+
18+
class ImplementsInheritedExisting implements ExtendingExistingOptional {}
19+
class ImplementsInheritedSkipped implements ExtendingNonexistantOptional {}
20+
21+
$classes = [
22+
ImplementingOptionalInterface::class,
23+
SkippingOptionalInterface::class,
24+
MultipleOptionalsExistingFirst::class,
25+
MultipleOptionalsExistingLast::class,
26+
MixedOptionalFirst::class,
27+
MixedOptionalLast::class,
28+
ImplementsInheritedExisting::class,
29+
ImplementsInheritedSkipped::class,
30+
];
31+
32+
foreach ($classes as $class) {
33+
echo "$class\n";
34+
echo ' class implements '.implode(', ', class_implements($class))."\n";
35+
echo ' object implements '.implode(', ', class_implements(new $class))."\n";
36+
}
37+
38+
$interfaces = [
39+
ExtendingExistingOptional::class,
40+
ExtendingNonexistantOptional::class,
41+
];
42+
43+
foreach ($interfaces as $interface) {
44+
echo "$interface\n";
45+
echo ' interface extends '.implode(', ', class_implements($class))."\n";
46+
}
47+
48+
?>
49+
--EXPECT--
50+
ImplementingOptionalInterface
51+
class implements ExistingInterface
52+
object implements ExistingInterface
53+
SkippingOptionalInterface
54+
class implements
55+
object implements
56+
MultipleOptionalsExistingFirst
57+
class implements ExistingInterface
58+
object implements ExistingInterface
59+
MultipleOptionalsExistingLast
60+
class implements ExistingInterface
61+
object implements ExistingInterface
62+
MixedOptionalFirst
63+
class implements ExistingInterface
64+
object implements ExistingInterface
65+
MixedOptionalLast
66+
class implements ExistingInterface
67+
object implements ExistingInterface
68+
ImplementsInheritedExisting
69+
class implements ExtendingExistingOptional, ExistingInterface
70+
object implements ExtendingExistingOptional, ExistingInterface
71+
ImplementsInheritedSkipped
72+
class implements ExtendingNonexistantOptional
73+
object implements ExtendingNonexistantOptional
74+
ExtendingExistingOptional
75+
interface extends ExtendingNonexistantOptional
76+
ExtendingNonexistantOptional
77+
interface extends ExtendingNonexistantOptional
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Optional interfaces are checked
3+
--FILE--
4+
<?php
5+
6+
interface Iface
7+
{
8+
public function method();
9+
}
10+
11+
class DecentClass implements ?Iface
12+
{
13+
public function method() {}
14+
}
15+
16+
$anInstance = new DecentClass;
17+
if ($anInstance instanceof Iface)
18+
echo "Existing interfaces can be implemented.";
19+
20+
class BadClass implements ?Iface {}
21+
22+
?>
23+
--EXPECTF--
24+
Existing interfaces can be implemented.
25+
Fatal error: Class BadClass contains 1 abstract method and must therefore be declared abstract or implement the remaining method (Iface::method) in %soptional_interfaces_are_checked.php on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Optional interfaces doesn't make Override optional
3+
--FILE--
4+
<?php
5+
6+
interface ExistingInterface
7+
{
8+
public function method();
9+
}
10+
11+
class TestClass implements ?ExistingInterface, ?NonExistantInterface
12+
{
13+
#[\Override]
14+
public function method() {}
15+
16+
#[\Override]
17+
public function other() {}
18+
}
19+
20+
?>
21+
--EXPECTF--
22+
Fatal error: TestClass::other() has #[\Override] attribute, but no matching parent method exists in %soptional_interfaces/override.php on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Reflection works on optional interfaces
3+
--FILE--
4+
<?php
5+
6+
interface ExistingInterface {}
7+
8+
class TestClass implements ?ExistingInterface, ?NonexistantInterface {}
9+
10+
$reflection = new ReflectionClass('TestClass');
11+
echo implode(', ', $reflection->getInterfaceNames());
12+
13+
?>
14+
--EXPECT--
15+
ExistingInterface
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Classes with optional interfaces survive serialization
3+
--FILE--
4+
<?php
5+
6+
interface ExistingInterface {}
7+
class TestClass implements ?ExistingInterface, ?NonExistantInterface {}
8+
9+
$original = new TestClass;
10+
$deserialized = unserialize(serialize($original));
11+
12+
echo implode(',', class_implements($deserialized));
13+
14+
?>
15+
--EXPECT--
16+
ExistingInterface
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Only the existing interfaces pass the type checks
3+
--INI--
4+
zend.exception_ignore_args = On
5+
--FILE--
6+
<?php
7+
8+
interface ExistingInterface {}
9+
10+
class TestClass implements ?ExistingInterface, ?NonExistantInterface {}
11+
12+
function f1(ExistingInterface $x) { echo "F1"; }
13+
function f2(NonExistantInterface $x) { echo "F2"; }
14+
15+
$c = new TestClass;
16+
17+
f1($c);
18+
f2($c)
19+
20+
?>
21+
--EXPECTF--
22+
F1
23+
Fatal error: Uncaught TypeError: f2(): Argument #1 ($x) must be of type NonExistantInterface, TestClass given, called in %stype_checks.php on line %d and defined in %stype_checks.php:%d
24+
Stack trace:
25+
#0 %stype_checks.php(%d): f2(Object(TestClass))
26+
#1 {main}
27+
thrown in %stype_checks.php on line %d

0 commit comments

Comments
 (0)