- Move large array-valued class constant initialization from module_init() to isolated functions - Generate independent cacheable translation units for request-lifecycle helpers - Add static zend_class_entry pointers with null initialization for module entry unit - Preserve arginfo-backed class registration in extension-*.cc while splitting array constants - Implement proper cleanup of split lifecycle sources during incremental builds - Cache PHP SDK discovery results to avoid repeated php-config invocations - Add comprehensive tests for class constant lifecycle splitting behavior - Verify static class entry declarations do not expose unnecessary linkable symbolsmaster
parent
55bfcc2ea2
commit
5d4f31a9ab
6 changed files with 446 additions and 72 deletions
@ -0,0 +1,155 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use TypePhp\CompilerBase; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
|
||||||
|
final class ClassConstantLifecycleSplitTest extends BaseTest |
||||||
|
{ |
||||||
|
private string $projectDir; |
||||||
|
|
||||||
|
protected function setUp(): void |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
$this->projectDir = sys_get_temp_dir() . '/typephp_class_constant_split_' |
||||||
|
. bin2hex(random_bytes(6)); |
||||||
|
mkdir($this->projectDir, 0777, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown(): void |
||||||
|
{ |
||||||
|
$this->removeDirectory($this->projectDir); |
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
public function testArrayConstantLifecycleIsSplitWithoutMovingArginfoRegistration(): void |
||||||
|
{ |
||||||
|
$parent = $this->writeSource('parent.php', <<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
class LifecycleParent |
||||||
|
{ |
||||||
|
public const array ITEMS = ['ready']; |
||||||
|
} |
||||||
|
PHP); |
||||||
|
$child = $this->writeSource('child.php', <<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
class LifecycleChild extends LifecycleParent |
||||||
|
{ |
||||||
|
} |
||||||
|
PHP); |
||||||
|
|
||||||
|
global $translator; |
||||||
|
$compiler = CompilerTest::create($this->projectDir); |
||||||
|
$translator = $compiler; |
||||||
|
$compiler->setBuildMode(CompilerBase::BUILD_MODE_EXT); |
||||||
|
$compiler->setTargetName('lifecycle_split'); |
||||||
|
$compiler->addFiles([$parent, $child]); |
||||||
|
foreach ([$parent, $child] as $file) { |
||||||
|
$compiler->prepareFile($file); |
||||||
|
} |
||||||
|
|
||||||
|
$sources = $compiler->convert($compiler->getSortedFiles([$parent, $child])); |
||||||
|
$lifecycleSources = array_values(array_filter( |
||||||
|
$sources, |
||||||
|
static fn (string $source): bool => str_contains($source, '/init/') |
||||||
|
&& str_ends_with($source, '_class_constants.cc'), |
||||||
|
)); |
||||||
|
|
||||||
|
self::assertCount(2, $lifecycleSources); |
||||||
|
$lifecycleCode = implode(PHP_EOL, array_map('file_get_contents', $lifecycleSources)); |
||||||
|
self::assertStringContainsString( |
||||||
|
'zend_never_inline void typephp_request_init_class_constant_', |
||||||
|
$lifecycleCode, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
'php::updateConstant("LifecycleParent", "ITEMS"', |
||||||
|
$lifecycleCode, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
'php::updateConstant("LifecycleChild", "ITEMS"', |
||||||
|
$lifecycleCode, |
||||||
|
); |
||||||
|
self::assertStringNotContainsString('_arginfo.h>', $lifecycleCode); |
||||||
|
|
||||||
|
$extension = (string) file_get_contents( |
||||||
|
$this->projectDir . '/build/extension-lifecycle_split.cc', |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
$compiler->getArgInfoHeaderFile($parent, true) . '>', |
||||||
|
$extension, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
$compiler->getArgInfoHeaderFile($child, true) . '>', |
||||||
|
$extension, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
'php_class_entry_LifecycleParent = php_register_class_LifecycleParent();', |
||||||
|
$extension, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
'zend_never_inline void typephp_request_init_class_constant_', |
||||||
|
$extension, |
||||||
|
); |
||||||
|
|
||||||
|
$moduleInitStart = strpos($extension, 'static void module_init()'); |
||||||
|
$moduleCleanStart = strpos($extension, 'static void module_clean()'); |
||||||
|
self::assertIsInt($moduleInitStart); |
||||||
|
self::assertIsInt($moduleCleanStart); |
||||||
|
$moduleInit = substr($extension, $moduleInitStart, $moduleCleanStart - $moduleInitStart); |
||||||
|
self::assertStringContainsString('typephp_request_init_class_constant_', $moduleInit); |
||||||
|
self::assertStringNotContainsString('php::updateConstant("LifecycleParent"', $moduleInit); |
||||||
|
|
||||||
|
// Removing the last array-valued constant must also remove generated |
||||||
|
// lifecycle sources from the previous incremental graph. |
||||||
|
$this->writeSource('parent.php', <<<'PHP' |
||||||
|
<?php |
||||||
|
|
||||||
|
class LifecycleParent |
||||||
|
{ |
||||||
|
public const int ITEMS = 1; |
||||||
|
} |
||||||
|
PHP); |
||||||
|
$nextCompiler = CompilerTest::create($this->projectDir); |
||||||
|
$translator = $nextCompiler; |
||||||
|
$nextCompiler->setBuildMode(CompilerBase::BUILD_MODE_EXT); |
||||||
|
$nextCompiler->setTargetName('lifecycle_split'); |
||||||
|
$nextCompiler->addFiles([$parent, $child]); |
||||||
|
foreach ([$parent, $child] as $file) { |
||||||
|
$nextCompiler->prepareFile($file); |
||||||
|
} |
||||||
|
$nextSources = $nextCompiler->convert($nextCompiler->getSortedFiles([$parent, $child])); |
||||||
|
self::assertSame( |
||||||
|
[], |
||||||
|
array_values(array_filter( |
||||||
|
$nextSources, |
||||||
|
static fn (string $source): bool => str_contains($source, '/init/') |
||||||
|
&& str_ends_with($source, '_class_constants.cc'), |
||||||
|
)), |
||||||
|
); |
||||||
|
self::assertSame([], glob($this->projectDir . '/build/init/*_class_constants.cc')); |
||||||
|
} |
||||||
|
|
||||||
|
private function writeSource(string $name, string $code): string |
||||||
|
{ |
||||||
|
$file = $this->projectDir . '/' . $name; |
||||||
|
file_put_contents($file, $code); |
||||||
|
return $file; |
||||||
|
} |
||||||
|
|
||||||
|
private function removeDirectory(string $directory): void |
||||||
|
{ |
||||||
|
if (!is_dir($directory)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
foreach (array_diff(scandir($directory), ['.', '..']) as $entry) { |
||||||
|
$path = $directory . '/' . $entry; |
||||||
|
if (is_dir($path)) { |
||||||
|
$this->removeDirectory($path); |
||||||
|
} else { |
||||||
|
unlink($path); |
||||||
|
} |
||||||
|
} |
||||||
|
rmdir($directory); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue