- Add test cases for forbidden magic methods in enums (__construct, __destruct, __clone, etc.) - Create tests for trait-injected forbidden methods failing during composition - Add tests for trait aliases to forbidden methods detection - Verify __call, __callStatic and __invoke remain allowed in enums - Include proper setup and teardown for temporary test directories - Provide data provider for forbidden magic method testing scenarios feat(enum): enforce forbidden magic method restrictions on enums - Add ENUM_FORBIDDEN_MAGIC_METHODS constant with restricted methods list - Implement assertEnumMayIncludeMethod to validate enum method declarations - Prevent forbidden magic methods from being added to enums during preprocessing - Share validation logic between direct method declarations and trait composition - Addmaster
parent
adc3162a22
commit
5f75c5b48c
3 changed files with 235 additions and 0 deletions
@ -0,0 +1,193 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
use TypePhp\CompilerTest; |
||||
use TypePhp\Exception\TestError; |
||||
|
||||
/** |
||||
* Enum method restrictions are declaration rules. They must fail in the |
||||
* TypePHP front end, including methods injected by Trait composition, rather |
||||
* than reaching Zend class registration during module startup. |
||||
* @internal |
||||
* @coversNothing |
||||
*/ |
||||
final class EnumMethodDeclarationRulesTest extends PHPUnit\Framework\TestCase |
||||
{ |
||||
private string $testRoot; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
$this->testRoot = sys_get_temp_dir() . '/typephp-enum-method-' . bin2hex(random_bytes(8)); |
||||
mkdir($this->testRoot, 0777, true); |
||||
} |
||||
|
||||
protected function tearDown(): void |
||||
{ |
||||
if (!is_dir($this->testRoot)) { |
||||
return; |
||||
} |
||||
|
||||
$iterator = new RecursiveIteratorIterator( |
||||
new RecursiveDirectoryIterator($this->testRoot, FilesystemIterator::SKIP_DOTS), |
||||
RecursiveIteratorIterator::CHILD_FIRST, |
||||
); |
||||
foreach ($iterator as $entry) { |
||||
if ($entry->isDir()) { |
||||
rmdir($entry->getPathname()); |
||||
} else { |
||||
unlink($entry->getPathname()); |
||||
} |
||||
} |
||||
rmdir($this->testRoot); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider forbiddenMagicMethodProvider |
||||
*/ |
||||
public function testForbiddenMagicMethodFailsDuringPrepare(string $method): void |
||||
{ |
||||
$source = <<<PHP |
||||
<?php |
||||
enum Suit |
||||
{ |
||||
case Hearts; |
||||
public function {$method}() {} |
||||
} |
||||
|
||||
function main(): void {} |
||||
PHP; |
||||
|
||||
[$compiler, $file] = $this->compilerFor($source); |
||||
|
||||
$this->expectException(TestError::class); |
||||
$this->expectExceptionMessage("Enum `Suit` cannot include magic method `{$method}`"); |
||||
$compiler->prepareFile($file); |
||||
} |
||||
|
||||
public static function forbiddenMagicMethodProvider(): iterable |
||||
{ |
||||
foreach ([ |
||||
'__construct', |
||||
'__destruct', |
||||
'__clone', |
||||
'__get', |
||||
'__set', |
||||
'__unset', |
||||
'__isset', |
||||
'__sleep', |
||||
'__wakeup', |
||||
'__set_state', |
||||
'__serialize', |
||||
'__unserialize', |
||||
'__ToString', |
||||
'__debugInfo', |
||||
] as $method) { |
||||
yield $method => [$method]; |
||||
} |
||||
} |
||||
|
||||
public function testTraitInjectedForbiddenMethodFailsDuringComposition(): void |
||||
{ |
||||
$source = <<<'PHP' |
||||
<?php |
||||
trait Builder |
||||
{ |
||||
public function __construct() {} |
||||
} |
||||
|
||||
enum Suit |
||||
{ |
||||
use Builder; |
||||
case Hearts; |
||||
} |
||||
|
||||
function main(): void {} |
||||
PHP; |
||||
|
||||
[$compiler, $file] = $this->compilerFor($source); |
||||
$compiler->prepareFile($file); |
||||
|
||||
$this->expectException(TestError::class); |
||||
$this->expectExceptionMessage('Enum `Suit` cannot include magic method `__construct`'); |
||||
$compiler->composeTraitDeclarations([$file]); |
||||
} |
||||
|
||||
public function testTraitAliasToForbiddenMethodFailsDuringComposition(): void |
||||
{ |
||||
$source = <<<'PHP' |
||||
<?php |
||||
trait Builder |
||||
{ |
||||
public function cleanup(): void {} |
||||
} |
||||
|
||||
enum Suit |
||||
{ |
||||
use Builder { cleanup as __destruct; } |
||||
case Hearts; |
||||
} |
||||
|
||||
function main(): void {} |
||||
PHP; |
||||
|
||||
[$compiler, $file] = $this->compilerFor($source); |
||||
$compiler->prepareFile($file); |
||||
|
||||
$this->expectException(TestError::class); |
||||
$this->expectExceptionMessage('Enum `Suit` cannot include magic method `__destruct`'); |
||||
$compiler->composeTraitDeclarations([$file]); |
||||
} |
||||
|
||||
public function testCallCallStaticAndInvokeRemainAllowed(): void |
||||
{ |
||||
$source = <<<'PHP' |
||||
<?php |
||||
enum Suit |
||||
{ |
||||
case Hearts; |
||||
|
||||
public function __call(string $name, array $arguments): mixed |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public static function __callStatic(string $name, array $arguments): mixed |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public function __invoke(): string |
||||
{ |
||||
return 'Hearts'; |
||||
} |
||||
} |
||||
|
||||
function main(): void {} |
||||
PHP; |
||||
|
||||
[$compiler, $file] = $this->compilerFor($source); |
||||
$compiler->prepareFile($file); |
||||
$compiler->convertFile($file); |
||||
|
||||
self::assertFileExists($compiler->getCppFile($file)); |
||||
} |
||||
|
||||
/** @return array{CompilerTest, string} */ |
||||
private function compilerFor(string $source): array |
||||
{ |
||||
$file = $this->testRoot . '/program.php'; |
||||
file_put_contents($file, $source); |
||||
|
||||
global $translator; |
||||
$compiler = CompilerTest::create($this->testRoot); |
||||
$translator = $compiler; |
||||
$compiler->addFiles([$file]); |
||||
|
||||
return [$compiler, $file]; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue