- 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