refactor(build): split class array constant lifecycle into separate translation units

- 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 symbols
master
韩天峰 4 weeks ago
parent 55bfcc2ea2
commit 5d4f31a9ab
  1. 155
      phpunit/src/ClassConstantLifecycleSplitTest.php
  2. 8
      phpunit/src/InternalParentExtensionCodegenTest.php
  3. 23
      phpunit/src/Platform/PlatformTest.php
  4. 7
      src/Build/SourcePipelineTrait.php
  5. 48
      src/Platform/UnixPlatform.php
  6. 277
      src/Translator.php

@ -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);
}
}

@ -42,6 +42,14 @@ PHP);
$extension = file_get_contents($compiler->genExtension());
self::assertStringContainsString(
'static zend_class_entry *php_class_entry_ArrayObject = nullptr;',
$extension,
);
self::assertStringContainsString(
'static zend_class_entry *php_class_entry_InternalParentChild = nullptr;',
$extension,
);
self::assertStringContainsString(
'php_class_entry_ArrayObject = php::getInternalClassEntrySafe("ArrayObject");',
$extension,

@ -398,8 +398,9 @@ class PlatformTest extends TestCase
mkdir($wrongInclude, 0755, true);
$versionedConfig = $phpHome . '/bin/php-config' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
$queryLog = $root . '/queries.log';
$wrongMinor = PHP_MINOR_VERSION === 4 ? 5 : 4;
$this->writePhpConfig($versionedConfig, PHP_VERSION, $phpHome, $rightInclude);
$this->writePhpConfig($versionedConfig, PHP_VERSION, $phpHome, $rightInclude, $queryLog);
$this->writePhpConfig(
$phpHome . '/bin/php-config',
PHP_MAJOR_VERSION . '.' . $wrongMinor . '.0',
@ -414,11 +415,26 @@ class PlatformTest extends TestCase
$this->assertSame($phpHome, $platform->getPhpDir());
$this->assertSame([$rightInclude], $platform->buildPhpIncludePaths($phpHome));
for ($i = 0; $i < 10; ++$i) {
$this->assertSame($phpHome, $platform->getPhpDir());
$this->assertSame([$rightInclude], $platform->buildPhpIncludePaths($phpHome));
}
$this->assertSame("--version\n--includes\n", file_get_contents($queryLog));
// A changed authoritative environment must not reuse the old SDK.
putenv('PHP_HOME=' . $root . '/missing');
try {
$platform->getPhpDir();
$this->fail('Expected changed PHP_HOME to be validated');
} catch (\RuntimeException $e) {
$this->assertStringContainsString('PHP_HOME is not a directory', $e->getMessage());
}
} finally {
$previousPhpHome === false
? putenv('PHP_HOME')
: putenv('PHP_HOME=' . $previousPhpHome);
unlink($versionedConfig);
unlink($queryLog);
unlink($phpHome . '/bin/php-config');
rmdir($rightInclude);
rmdir($wrongInclude);
@ -455,7 +471,7 @@ class PlatformTest extends TestCase
}
}
private function writePhpConfig(string $path, string $version, string $prefix, string $include): void
private function writePhpConfig(string $path, string $version, string $prefix, string $include, ?string $queryLog = null): void
{
$script = sprintf(
"#!/bin/sh\ncase \"\$1\" in\n --version) printf '%%s\\n' %s ;;\n --prefix) printf '%%s\\n' %s ;;\n --includes) printf '%%s\\n' %s ;;\nesac\n",
@ -463,6 +479,9 @@ class PlatformTest extends TestCase
escapeshellarg($prefix),
escapeshellarg('-I' . $include),
);
if ($queryLog !== null) {
$script = str_replace("#!/bin/sh\n", "#!/bin/sh\nprintf '%s\\n' \"\$1\" >> " . escapeshellarg($queryLog) . "\n", $script);
}
file_put_contents($path, $script);
chmod($path, 0755);
}

@ -428,6 +428,13 @@ trait SourcePipelineTrait
// Function and data declarations are emitted together, one header
// per PHP source, plus a small project-runtime ABI header.
$this->genDeclarationHeaders($files);
// Large array-valued class constants used to make module_init() one
// enormous GCC optimization unit. Emit their request-lifecycle
// helpers as independent, cacheable translation units while the
// arginfo-backed class registration remains in extension-*.cc.
foreach ($this->genClassArrayConstantLifecycleSources() as $lifecycleSource) {
$sourceFiles[] = $lifecycleSource;
}
// Nano keeps the ordinary statically registered Zend class/module
// metadata, then adds a direct native process entry beside it.
$sourceFiles[] = $this->genExtension();

@ -8,6 +8,22 @@ namespace TypePhp\Platform;
*/
abstract class UnixPlatform extends PlatformBase
{
/** @var array<string, string> */
private array $phpDirectoryCache = [];
/** @var array<string, string> */
private array $phpConfigCache = [];
/** @var array<string, list<string>> */
private array $phpIncludeCache = [];
/** @var array<string, string> */
private array $phpConfigValueCache = [];
private function getPhpSdkCacheKey(string $directory = ''): string
{
// Scope discovery to this platform instance and the selected environment.
// Constructing every compile/cache-key command must not rerun php-config.
return serialize([$directory, getenv('PHP_HOME'), getenv('PATH')]);
}
public function getSharedLinkFlag(): string
{
return '-shared';
@ -97,6 +113,12 @@ abstract class UnixPlatform extends PlatformBase
}
public function getPhpDir(): string
{
$key = $this->getPhpSdkCacheKey();
return $this->phpDirectoryCache[$key] ??= $this->resolvePhpDir();
}
private function resolvePhpDir(): string
{
$phpDir = getenv('PHP_HOME');
if (is_string($phpDir) && $phpDir !== '') {
@ -173,6 +195,12 @@ abstract class UnixPlatform extends PlatformBase
* Build the PHP include paths (obtained dynamically via php-config).
*/
public function buildPhpIncludePaths(string $phpDir): array
{
$key = $this->getPhpSdkCacheKey($phpDir);
return $this->phpIncludeCache[$key] ??= $this->resolvePhpIncludePaths($phpDir);
}
private function resolvePhpIncludePaths(string $phpDir): array
{
$phpConfigPath = $this->findPhpConfig($phpDir);
if ($phpConfigPath) {
@ -213,6 +241,13 @@ abstract class UnixPlatform extends PlatformBase
* Locate the php-config executable.
*/
protected function findPhpConfig(string $phpDir): ?string
{
$key = $this->getPhpSdkCacheKey($phpDir);
// Do not cache failed discovery: an SDK may be installed afterwards.
return $this->phpConfigCache[$key] ??= $this->resolvePhpConfig($phpDir);
}
private function resolvePhpConfig(string $phpDir): ?string
{
$candidates = [];
$phpDir = rtrim($phpDir, '/');
@ -263,6 +298,13 @@ abstract class UnixPlatform extends PlatformBase
$candidates[] = $versioned;
}
// Validate preferred installations before invoking the PATH fallback.
foreach (array_unique($candidates) as $config) {
if ($this->phpConfigMatchesCurrentPhp($config)) {
return $config;
}
}
// PATH is only a fallback, and its prefix must match the selected PHP.
$whichResult = trim(shell_exec('which php-config 2>/dev/null'));
if ($whichResult && is_executable($whichResult)) {
@ -325,11 +367,15 @@ abstract class UnixPlatform extends PlatformBase
protected function getPhpConfigValue(string $phpConfig, string $option): ?string
{
$key = $this->getPhpSdkCacheKey($phpConfig) . "\0" . $option;
if (isset($this->phpConfigValueCache[$key])) {
return $this->phpConfigValueCache[$key];
}
$value = shell_exec(escapeshellarg($phpConfig) . ' ' . escapeshellarg($option) . ' 2>/dev/null');
if (!is_string($value) || trim($value) === '') {
return null;
}
return trim($value);
return $this->phpConfigValueCache[$key] = trim($value);
}
protected function resolvePhpLibDir(string $phpDir): ?string

@ -102,6 +102,17 @@ class Translator extends Preprocessor
/** Generated per-file teardown functions for persistent AST class constants. */
protected array $releaseAstConstantFns = [];
/** @var list<string> Cross-TU helpers called by module_init(). */
private array $classArrayConstantInitCalls = [];
/** @var list<string> Cross-TU helpers called by module_clean(). */
private array $classArrayConstantCleanCalls = [];
/** @var list<string> Forward declarations for split request-lifecycle helpers. */
private array $classArrayConstantLifecycleDeclarations = [];
private bool $classArrayConstantLifecycleSplit = false;
// Windows resource file configuration (icon, version info, etc.)
protected array $resourceConfig = [];
@ -1184,6 +1195,12 @@ class Translator extends Preprocessor
// whole-program property-default invariant local to the consumer too.
$this->finalizeRequestArrayDefaultMetadata();
$this->genClassCeList();
// Clean incremental metadata is restored in source-path order, while a
// full conversion discovers globals in dependency order. Definitions,
// request registration and cleanup have no ordering dependency, so use
// one canonical order and keep extension-*.cc byte-stable across both.
$extensionGlobalVars = $this->globalVars;
ksort($extensionGlobalVars, SORT_STRING);
$this->indentLevel++;
$code = $this->genExtensionIncludeHeaderFiles();
@ -1217,7 +1234,7 @@ class Translator extends Preprocessor
$code .= 'namespace ' . $projectNamespace . ' {' . PHP_EOL . PHP_EOL;
$code .= "// global vars \n";
foreach ($this->globalVars as $name => $type) {
foreach ($extensionGlobalVars as $name => $type) {
$cppType = isset($this->nativeGlobalObjects[$name])
? $this->getNativeObjectPointerType($this->nativeGlobalObjects[$name])
: Type::VAR;
@ -1230,7 +1247,11 @@ class Translator extends Preprocessor
$code .= "// class register functions \n";
foreach ($this->classCeList as $ce) {
$code .= 'zend_class_entry *' . $ce . ';' . PHP_EOL;
// These slots are consumed only by the module-entry translation
// unit. Generated project sources resolve classes through the
// request/persistent cache accessors instead, so exposing one
// linkable data symbol per class is unnecessary.
$code .= 'static zend_class_entry *' . $ce . ' = nullptr;' . PHP_EOL;
}
$code .= "// request-local caches \n";
@ -1403,6 +1424,12 @@ CODE;
}
$code .= $this->genRequestArrayDefaultInitializers();
if ($this->classArrayConstantLifecycleDeclarations !== []) {
$code .= "// split request-lifecycle helpers\n";
$code .= implode(PHP_EOL, $this->classArrayConstantLifecycleDeclarations)
. PHP_EOL . PHP_EOL;
}
$traitMetadata = $this->genTraitMetadataCode();
$code .= $traitMetadata['declarations'];
@ -1537,7 +1564,7 @@ CODE;
$code .= 'php::fn::define(' . $this->genCharPtr($const->name, true) . ', ' . $name . ');' . PHP_EOL;
}
$code .= '// global vars ' . PHP_EOL;
foreach ($this->globalVars as $name => $type) {
foreach ($extensionGlobalVars as $name => $type) {
if ($name == 'GLOBALS') {
continue;
}
@ -1591,7 +1618,7 @@ CODE;
// request-level module state cleanup
$code .= 'static void module_clean() {' . PHP_EOL;
foreach ($this->globalVars as $name => $type) {
foreach ($extensionGlobalVars as $name => $type) {
if ($name != 'GLOBALS') {
if (isset($this->nativeGlobalObjects[$name])) {
$code .= $this->escapeGlobalVar($name) . ' = nullptr;' . PHP_EOL;
@ -1615,62 +1642,7 @@ CODE;
$code .= $this->genPythonModuleCleanup();
$code .= '// class array constants' . PHP_EOL;
foreach ($this->getClassLikesWithConstants() as $classDef) {
foreach ($classDef->constants as $constant) {
if ($constant->type === Type::ARRAY) {
$constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name);
$code .= $constName . ".unset();\n";
if (!$classDef instanceof ClassDef || !$classDef->nativeObject) {
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n";
}
}
}
}
// Clean up inherited array constants from child classes
foreach ($this->symbols->classes() as $className => $classDef) {
if ($classDef->nativeObject) {
continue;
}
$ownConstNames = [];
foreach ($classDef->constants as $constant) {
if ($constant->type === Type::ARRAY) {
$ownConstNames[$constant->name] = true;
}
}
$parentName = $this->escapeClass($classDef->extends);
while ($parentName && $this->symbols->hasClass($parentName)) {
$parentDef = $this->symbols->class($parentName);
foreach ($parentDef->constants as $constant) {
if ($constant->type === Type::ARRAY && !isset($ownConstNames[$constant->name])) {
$ownConstNames[$constant->name] = true;
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n";
}
}
$parentName = $this->escapeClass($parentDef->extends);
}
foreach ($this->getClassImplementedInterfaces($classDef) as $interfaceName) {
if (!$this->hasInterface($interfaceName)) {
continue;
}
$interfaceDef = $this->getInterface($interfaceName);
foreach ($interfaceDef->constants as $constant) {
if ($constant->type === Type::ARRAY && !isset($ownConstNames[$constant->name])) {
$ownConstNames[$constant->name] = true;
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n";
}
}
}
}
$code .= $this->genClassArrayConstantCleanup();
$code .= '}' . PHP_EOL . PHP_EOL;
// module_clean end
@ -3460,20 +3432,173 @@ CODE;
protected function genClassArrayConstants(): string
{
$code = '';
if ($this->classArrayConstantLifecycleSplit) {
return implode(PHP_EOL, $this->classArrayConstantInitCalls)
. ($this->classArrayConstantInitCalls === [] ? '' : PHP_EOL);
}
return implode('', array_column($this->getClassArrayConstantLifecycleOperations(), 'init'));
}
protected function genClassArrayConstantCleanup(): string
{
if ($this->classArrayConstantLifecycleSplit) {
return implode(PHP_EOL, $this->classArrayConstantCleanCalls)
. ($this->classArrayConstantCleanCalls === [] ? '' : PHP_EOL);
}
return implode('', array_column($this->getClassArrayConstantLifecycleOperations(), 'clean'));
}
/**
* Keep the arginfo-defined php_register_class_* functions in the extension
* translation unit, but isolate the much larger request-time array builders.
* Each constant gets its own non-inline function so GCC's points-to analysis
* cannot reconstruct the former multi-thousand-line module_init() under LTO.
*
* @return list<string>
*/
public function genClassArrayConstantLifecycleSources(): array
{
$this->classArrayConstantInitCalls = [];
$this->classArrayConstantCleanCalls = [];
$this->classArrayConstantLifecycleDeclarations = [];
$this->classArrayConstantLifecycleSplit = true;
/** @var array<string, list<string>> $definitions */
$definitions = [];
/** @var array<string, array<string, true>> $dependencyFiles */
$dependencyFiles = [];
foreach ($this->getClassArrayConstantLifecycleOperations() as $operation) {
$sourceFile = $operation['sourceFile'];
if ($sourceFile === '' || !isset($this->declarationHeaderFiles[$sourceFile])) {
// Project class-likes normally always have an owner. Falling
// back to the extension keeps the generator correct for direct
// embedding APIs that construct symbols without source files.
$this->classArrayConstantInitCalls[] = $operation['init'];
$this->classArrayConstantCleanCalls[] = $operation['clean'];
continue;
}
$suffix = substr(hash('sha256', $sourceFile . "\0" . $operation['key']), 0, 20);
$initFunction = 'typephp_request_init_class_constant_' . $suffix;
$cleanFunction = 'typephp_request_clean_class_constant_' . $suffix;
$this->classArrayConstantLifecycleDeclarations[] =
"zend_never_inline void {$initFunction}();";
$this->classArrayConstantLifecycleDeclarations[] =
"zend_never_inline void {$cleanFunction}();";
$this->classArrayConstantInitCalls[] = "{$initFunction}();";
$this->classArrayConstantCleanCalls[] = "{$cleanFunction}();";
$definitions[$sourceFile][] = "zend_never_inline void {$initFunction}() {\n"
. $operation['init'] . "}\n\n";
$definitions[$sourceFile][] = "zend_never_inline void {$cleanFunction}() {\n"
. $operation['clean'] . "}\n\n";
foreach ($operation['dependencies'] as $dependencyFile) {
if ($dependencyFile !== '' && $dependencyFile !== $sourceFile) {
$dependencyFiles[$sourceFile][$dependencyFile] = true;
}
}
}
$sources = [];
foreach ($definitions as $sourceFile => $sourceDefinitions) {
$headers = [
...$this->getGeneratedSourceGlobalHeaders(),
$this->getRuntimeDeclarationHeaderName(),
...$this->getDeclarationHeadersForFile($sourceFile),
];
foreach (array_keys($dependencyFiles[$sourceFile] ?? []) as $dependencyFile) {
if (isset($this->declarationHeaderFiles[$dependencyFile])) {
$headers[] = $this->declarationHeaderFiles[$dependencyFile];
}
}
$lifecycleSource = $this->getClassArrayConstantLifecycleSourceFile($sourceFile);
$code = $this->renderIncludeHeaderFiles($headers) . PHP_EOL;
$code .= 'namespace ' . $this->getProjectNamespace() . ' {' . PHP_EOL . PHP_EOL;
$code .= implode('', $sourceDefinitions);
$code .= '} // namespace ' . $this->getProjectNamespace() . PHP_EOL;
$this->writeFile($lifecycleSource, $code);
$this->formatCppCode($lifecycleSource);
$this->registerGeneratedProjectSource($lifecycleSource);
$sources[] = $lifecycleSource;
}
$this->removeStaleClassArrayConstantLifecycleSources($sources);
return $sources;
}
private function getClassArrayConstantLifecycleSourceFile(string $sourceFile): string
{
$header = $this->declarationHeaderFiles[$sourceFile]
?? basename($this->getDeclarationHeaderFile($sourceFile));
$stem = preg_replace('/_decl\\.h$/', '', basename($header)) ?: pathinfo($header, PATHINFO_FILENAME);
return $this->getBuildDir() . '/init/' . $stem . '_class_constants.cc';
}
/** @param list<string> $currentSources */
private function removeStaleClassArrayConstantLifecycleSources(array $currentSources): void
{
$manifest = $this->getBuildDir() . '/cache/incremental/' . $this->targetName
. '/class-constant-sources.json';
$previous = [];
if (is_file($manifest)) {
$decoded = json_decode((string) file_get_contents($manifest), true);
if (is_array($decoded)) {
$previous = array_values(array_filter($decoded, 'is_string'));
}
}
$current = array_map('basename', $currentSources);
foreach (array_diff($previous, $current) as $staleBasename) {
$staleSource = $this->getBuildDir() . '/init/' . basename($staleBasename);
$staleObject = $this->getObjectFile($staleSource);
foreach ([$staleSource, $staleObject, $this->getMiscObjectCacheMetadataFile($staleObject)] as $artifact) {
if (is_file($artifact)) {
@unlink($artifact);
}
}
}
$this->writeFile(
$manifest,
json_encode($current, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)
. PHP_EOL,
);
}
/**
* @return list<array{
* sourceFile: string,
* key: string,
* dependencies: list<string>,
* init: string,
* clean: string
* }>
*/
private function getClassArrayConstantLifecycleOperations(): array
{
$operations = [];
foreach ($this->getClassLikesWithConstants() as $classDef) {
foreach ($classDef->constants as $constant) {
if ($constant->type === Type::ARRAY) {
$constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name);
$code .= "do {\n";
$code .= $constant->arrayExpr;
$code .= $constName . ' = ' . $constant->value . ";\n";
$init = "do {\n";
$init .= $constant->arrayExpr;
$init .= $constName . ' = ' . $constant->value . ";\n";
$clean = $constName . ".unset();\n";
if (!$classDef instanceof ClassDef || !$classDef->nativeObject) {
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, {$constant->value});\n";
$init .= "php::updateConstant($classNameStr, $classConstStr, {$constant->value});\n";
$clean .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n";
}
$code .= "} while(0);\n";
$init .= "} while(0);\n";
$operations[] = [
'sourceFile' => $classDef->sourceFile,
'key' => 'own:' . $classDef->getNamespacedName(false) . ':' . $constant->name,
'dependencies' => [],
'init' => $init,
'clean' => $clean,
];
}
}
}
@ -3499,7 +3624,14 @@ CODE;
$constName = self::PREFIX . $this->getNativeName($constant->name, $parentDef->namespace, $parentDef->name);
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, {$constName});\n";
$operations[] = [
'sourceFile' => $classDef->sourceFile,
'key' => 'inherited:' . $classDef->getNamespacedName(false) . ':'
. $constant->name . ':' . $parentDef->getNamespacedName(false),
'dependencies' => [$parentDef->sourceFile],
'init' => "php::updateConstant($classNameStr, $classConstStr, {$constName});\n",
'clean' => "php::updateConstant($classNameStr, $classConstStr, php::null);\n",
];
}
}
$parentName = $this->escapeClass($parentDef->extends);
@ -3516,13 +3648,20 @@ CODE;
$constName = self::PREFIX . $this->getNativeName($constant->name, $interfaceDef->namespace, $interfaceDef->name);
$classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true);
$classConstStr = $this->genCharPtr($constant->name);
$code .= "php::updateConstant($classNameStr, $classConstStr, {$constName});\n";
$operations[] = [
'sourceFile' => $classDef->sourceFile,
'key' => 'interface:' . $classDef->getNamespacedName(false) . ':'
. $constant->name . ':' . $interfaceDef->getNamespacedName(false),
'dependencies' => [$interfaceDef->sourceFile],
'init' => "php::updateConstant($classNameStr, $classConstStr, {$constName});\n",
'clean' => "php::updateConstant($classNameStr, $classConstStr, php::null);\n",
];
}
}
}
}
return $code;
return $operations;
}
protected function getAbsolutePath(string $path, string $projectDir): string

Loading…
Cancel
Save