3v4l.org

run code in 500+ PHP versions simultaneously
<?php declare(strict_types = 1); namespace TradeoffDemo; class Foo { private string $secret = 'foo-secret'; public function hello(): string { return 'hello from ' . static::class; } } class SubFoo extends Foo { public function onlyInSubFoo(): string { return 'sub'; } } class NotFoo { } // Case 1: object/mixed bound into Foo scope is assumed to be Foo. function hydrate(object $object): mixed { return \Closure::bind(function () { if (!$this instanceof Foo) { return 'not a Foo: ' . get_class($this); } return $this->secret; }, $object, Foo::class)(); } // Case 2: a Foo bound into SubFoo scope is assumed to be SubFoo. function callSub(Foo $foo): mixed { return \Closure::bind(function () { if ($this instanceof SubFoo) { return $this->onlyInSubFoo(); } return 'plain Foo: ' . get_class($this); }, $foo, SubFoo::class)(); } // Case 3: the same, without a guard: PHPStan is silent, PHP fails at runtime. function readSecret(object $object): mixed { return \Closure::bind(fn () => $this->secret, $object, Foo::class)(); } function callSubUnguarded(Foo $foo): mixed { return \Closure::bind(fn () => $this->onlyInSubFoo(), $foo, SubFoo::class)(); } var_dump(hydrate(new Foo())); var_dump(hydrate(new NotFoo())); var_dump(callSub(new SubFoo())); var_dump(callSub(new Foo())); var_dump(readSecret(new NotFoo())); try { var_dump(callSubUnguarded(new Foo())); } catch (\Error $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; }
Output for 8.2.0 - 8.2.34, 8.3.0 - 8.3.35, 8.4.1 - 8.4.26, 8.5.0 - 8.5.11
string(10) "foo-secret" string(30) "not a Foo: TradeoffDemo\NotFoo" string(3) "sub" string(27) "plain Foo: TradeoffDemo\Foo" Warning: Undefined property: TradeoffDemo\NotFoo::$secret in /in/dhunm on line 54 NULL Error: Call to undefined method TradeoffDemo\Foo::onlyInSubFoo()

preferences:
32.51 ms | 1137 KiB | 7 Q