- Update ReturnInfo::equalsApartFromPhpDocAndRefcount to consider PHPDoc types in arginfo equivalence checks to prevent incorrect Reflection type exposure - Rename method from equalsApartFromPhpDocAndRefcount to equalsApartFromRefcount for better clarity and consistency - Modify Translator.php to use hasClass and hasInterface methods instead of symbols->hasClass and symbols->hasInterface for proper dependency resolution - Add comprehensive tests for class registration preserving reverse inheritance - Add tests for magic methods with implicit return types maintaining distinct Zend arginfo during registration - Include tests for interface registration handling child-before-parent scenarios - Add cross-namespace interface dependency resolution tests - Create diamond inheritance pattern registration preservation testspull/40/head
parent
2d4a1b8bb0
commit
0b1a45660e
8 changed files with 291 additions and 5 deletions
@ -0,0 +1,39 @@ |
||||
--TEST-- |
||||
class entry registration preserves reverse-declared class and interface dependencies |
||||
--FILE-- |
||||
<?php |
||||
|
||||
interface RegistrationMarker |
||||
{ |
||||
} |
||||
|
||||
class RegistrationLeaf extends RegistrationMiddle implements RegistrationMarker |
||||
{ |
||||
} |
||||
|
||||
class RegistrationMiddle extends RegistrationRoot |
||||
{ |
||||
} |
||||
|
||||
class RegistrationRoot |
||||
{ |
||||
public function name(): string |
||||
{ |
||||
return 'root'; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$value = new RegistrationLeaf(); |
||||
var_dump($value instanceof RegistrationMiddle); |
||||
var_dump($value instanceof RegistrationRoot); |
||||
var_dump($value instanceof RegistrationMarker); |
||||
echo $value->name(), "\n"; |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(true) |
||||
bool(true) |
||||
root |
||||
@ -0,0 +1,35 @@ |
||||
--TEST-- |
||||
class entry registration preserves a child interface dependency declared before its parent |
||||
--FILE-- |
||||
<?php |
||||
|
||||
namespace CacheContracts { |
||||
interface InvalidArgumentException extends CacheException |
||||
{ |
||||
} |
||||
|
||||
interface CacheException extends \Throwable |
||||
{ |
||||
} |
||||
|
||||
class ConcreteException extends \InvalidArgumentException implements InvalidArgumentException |
||||
{ |
||||
} |
||||
} |
||||
|
||||
namespace { |
||||
function main(): void |
||||
{ |
||||
$exception = new CacheContracts\ConcreteException('invalid cache key'); |
||||
var_dump($exception instanceof CacheContracts\InvalidArgumentException); |
||||
var_dump($exception instanceof CacheContracts\CacheException); |
||||
var_dump($exception instanceof Throwable); |
||||
echo $exception->getMessage(), "\n"; |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(true) |
||||
bool(true) |
||||
invalid cache key |
||||
@ -0,0 +1,35 @@ |
||||
--TEST-- |
||||
class entry registration resolves cross-namespace interface dependencies before implementors |
||||
--FILE-- |
||||
<?php |
||||
|
||||
namespace RegistrationConsumer { |
||||
use RegistrationContracts\BaseContract; |
||||
|
||||
interface ChildContract extends BaseContract |
||||
{ |
||||
} |
||||
|
||||
class Implementation implements ChildContract |
||||
{ |
||||
} |
||||
} |
||||
|
||||
namespace RegistrationContracts { |
||||
interface BaseContract |
||||
{ |
||||
} |
||||
} |
||||
|
||||
namespace { |
||||
function main(): void |
||||
{ |
||||
$value = new RegistrationConsumer\Implementation(); |
||||
var_dump($value instanceof RegistrationConsumer\ChildContract); |
||||
var_dump($value instanceof RegistrationContracts\BaseContract); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(true) |
||||
@ -0,0 +1,39 @@ |
||||
--TEST-- |
||||
class entry registration preserves all dependencies in a reverse-declared interface diamond |
||||
--FILE-- |
||||
<?php |
||||
|
||||
interface DiamondLeaf extends DiamondLeft, DiamondRight |
||||
{ |
||||
} |
||||
|
||||
interface DiamondRight extends DiamondRoot |
||||
{ |
||||
} |
||||
|
||||
interface DiamondLeft extends DiamondRoot |
||||
{ |
||||
} |
||||
|
||||
interface DiamondRoot |
||||
{ |
||||
} |
||||
|
||||
class DiamondImplementation implements DiamondLeaf |
||||
{ |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$value = new DiamondImplementation(); |
||||
var_dump($value instanceof DiamondLeaf); |
||||
var_dump($value instanceof DiamondLeft); |
||||
var_dump($value instanceof DiamondRight); |
||||
var_dump($value instanceof DiamondRoot); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(true) |
||||
bool(true) |
||||
bool(true) |
||||
@ -0,0 +1,76 @@ |
||||
--TEST-- |
||||
Magic methods with implicit return types keep distinct Zend arginfo |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class ImplicitMagicReturnTypes |
||||
{ |
||||
private array $values = ['answer' => 42]; |
||||
|
||||
public function render() |
||||
{ |
||||
return 'rendered'; |
||||
} |
||||
|
||||
public function __toString() |
||||
{ |
||||
return $this->render(); |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
public function __isset($name) |
||||
{ |
||||
return isset($this->values[$name]); |
||||
} |
||||
|
||||
/** |
||||
* @return void |
||||
*/ |
||||
public function __unset($name) |
||||
{ |
||||
unset($this->values[$name]); |
||||
} |
||||
|
||||
public function __set($name, $value) |
||||
{ |
||||
$this->values[$name] = $value; |
||||
} |
||||
|
||||
public function __serialize() |
||||
{ |
||||
return $this->values; |
||||
} |
||||
|
||||
public function __unserialize($data) |
||||
{ |
||||
$this->values = $data; |
||||
} |
||||
} |
||||
|
||||
function returnType(string $method): string |
||||
{ |
||||
$type = (new ReflectionMethod(ImplicitMagicReturnTypes::class, $method))->getReturnType(); |
||||
return $type ? (string) $type : 'none'; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$value = new ImplicitMagicReturnTypes(); |
||||
|
||||
var_dump((string) $value); |
||||
|
||||
foreach (['__toString', '__isset', '__unset', '__set', '__serialize', '__unserialize'] as $method) { |
||||
echo $method, ': ', returnType($method), PHP_EOL; |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(8) "rendered" |
||||
__toString: string |
||||
__isset: bool |
||||
__unset: void |
||||
__set: void |
||||
__serialize: array |
||||
__unserialize: void |
||||
@ -0,0 +1,54 @@ |
||||
--TEST-- |
||||
Implicit serialization magic method return types are not aliased by arginfo deduplication |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class ImplicitSerializationReturnTypes |
||||
{ |
||||
public function noReturnType() |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public function __serialize() |
||||
{ |
||||
return ['value' => 7]; |
||||
} |
||||
|
||||
public function __unserialize($data) |
||||
{ |
||||
} |
||||
|
||||
public function __sleep() |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
public function __wakeup() |
||||
{ |
||||
} |
||||
} |
||||
|
||||
function serializationReturnType(string $method): string |
||||
{ |
||||
$type = (new ReflectionMethod(ImplicitSerializationReturnTypes::class, $method))->getReturnType(); |
||||
return $type ? (string) $type : 'none'; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
foreach (['noReturnType', '__serialize', '__unserialize', '__sleep', '__wakeup'] as $method) { |
||||
echo $method, ': ', serializationReturnType($method), PHP_EOL; |
||||
} |
||||
|
||||
$serialized = serialize(new ImplicitSerializationReturnTypes()); |
||||
var_dump(str_contains($serialized, 'value')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
noReturnType: none |
||||
__serialize: array |
||||
__unserialize: void |
||||
__sleep: array |
||||
__wakeup: void |
||||
bool(true) |
||||
Loading…
Reference in new issue