From 7fa95d449f21bf805bf18e6c5cc1402e28dff58c Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 15:17:28 +0800 Subject: [PATCH 01/18] fix: preserve object property reference semantics --- src/Parser/AssignOpTrait.php | 27 ++- .../ref/property-reference-rebind.phpt | 170 ++++++++++++++++++ 2 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 tests/compiler/ref/property-reference-rebind.phpt diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 33232c7d..63dd112e 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -1303,7 +1303,26 @@ trait AssignOpTrait $this->assertReadonlyPropertyReferenceForbidden($expr->var, $expr, true); $this->assertReadonlyPropertyReferenceForbidden($expr->expr, $expr, false); - $left = $this->parseWritableIdentifier($expr->var); + $propertyReferenceTarget = null; + $nativeObjectProperty = $expr->var instanceof Expr\PropertyFetch + && $this->getNativePropertyClassDef($expr->var)?->nativeObject === true; + if ($expr->var instanceof Expr\PropertyFetch && !$nativeObjectProperty) { + // A property reference assignment must go through Zend's property + // metadata path. A plain Variant indirect slot cannot maintain + // typed-property reference sources safely. Parse the complete LHS + // before the RHS so PHP's source evaluation order is preserved. + $object = $this->parseOrderedOperand($expr->var->var, false); + $member = $this->isIdExpr($expr->var->name) + ? $this->propertyNameToStr($expr->var->name, literal: true) + : $this->parseOrderedOperand($expr->var->name, false, true); + $scope = $this->usesTraitPropertyScope($object) + ? 'php::FakeScopeGuard::current()' + : ($this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'); + $propertyReferenceTarget = [$object, $member, $scope]; + $left = ''; + } else { + $left = $this->parseWritableIdentifier($expr->var); + } // Keep this write-context form for every RHS kind. Re-parsing it as a // read later breaks append and missing-key targets such as // `$array[] =& $source`. @@ -1397,8 +1416,10 @@ trait AssignOpTrait } $this->context->beforeStmtLines[] = $rightExpr . ';'; - if ($expr->var instanceof Expr\PropertyFetch && $this->isNativePropertyAccess($expr->var)) { - return $left . '.rebindReference(' . $tmpVar . ')'; + if ($propertyReferenceTarget !== null) { + [$object, $member, $scope] = $propertyReferenceTarget; + return 'typephp_rebind_property_reference(' + . $object . ', ' . $member . ', ' . $tmpVar . ', ' . $scope . ')'; } return $left . ' = &' . $tmpVar; } diff --git a/tests/compiler/ref/property-reference-rebind.phpt b/tests/compiler/ref/property-reference-rebind.phpt new file mode 100644 index 00000000..4308f434 --- /dev/null +++ b/tests/compiler/ref/property-reference-rebind.phpt @@ -0,0 +1,170 @@ +--TEST-- +Object property reference assignment preserves aliases and typed-property sources +--FILE-- +value = &$source; + } + + public function value(): ?array + { + return $this->value; + } +} + +final class PropertyReferenceDep +{ + public ?array $map = null; +} + +function bindObjectProperty(object $holder, mixed &$source): void +{ + $holder->value = &$source; +} + +function replaceReference(mixed &$target, mixed $value): void +{ + $target = $value; +} + +function propertyReferenceTarget(array &$events, object $holder): object +{ + $events[] = 'object'; + return $holder; +} + +function propertyReferenceName(array &$events): string +{ + $events[] = 'property'; + return 'value'; +} + +function &propertyReferenceSource(array &$events, mixed &$source): mixed +{ + $events[] = 'source'; + return $source; +} + +function trackThroughSplObjectStorage(object $target): PropertyReferenceDep +{ + $storage = new SplObjectStorage(); + $storage->offsetSet($target, []); + $map = $storage[$target]; + + $dep = new PropertyReferenceDep(); + $dep->map = &$map; + $map['dep'] = $dep; + $storage->offsetSet($target, $map); + return $dep; +} + +function main(): void +{ + $holder = new PropertyReferenceHolder(); + $source = ['initial' => 1]; + $holder->value = &$source; + $source['source'] = 2; + $holder->value['property'] = 3; + var_dump($source); + + $dynamicHolder = new PropertyReferenceHolder(); + $dynamicSource = []; + bindObjectProperty($dynamicHolder, $dynamicSource); + $dynamicSource['dynamic'] = true; + var_dump($dynamicHolder->value); + + $wrong = 'invalid'; + try { + bindObjectProperty($dynamicHolder, $wrong); + echo "missing initial TypeError\n"; + } catch (TypeError $error) { + echo "initial TypeError\n"; + } + $dynamicSource['preserved'] = true; + var_dump($dynamicHolder->value); + + $replacement = ['replacement' => true]; + $holder->value = &$replacement; + replaceReference($source, 'detached'); + var_dump($source); + try { + replaceReference($replacement, 'invalid'); + echo "missing write TypeError\n"; + } catch (TypeError $error) { + echo "write TypeError\n"; + } + var_dump($holder->value); + + $privateSource = []; + $privateHolder = new PrivatePropertyReferenceHolder(); + $privateHolder->bind($privateSource); + $privateSource['private'] = true; + var_dump($privateHolder->value()); + + $events = []; + $orderedSource = []; + $orderedHolder = new PropertyReferenceHolder(); + propertyReferenceTarget($events, $orderedHolder)->{propertyReferenceName($events)} + = &propertyReferenceSource($events, $orderedSource); + $orderedSource['ordered'] = true; + var_dump($events, $orderedHolder->value); + + $dep = trackThroughSplObjectStorage(new stdClass()); + var_dump(isset($dep->map['dep'])); +} +?> +--EXPECT-- +array(3) { + ["initial"]=> + int(1) + ["source"]=> + int(2) + ["property"]=> + int(3) +} +array(1) { + ["dynamic"]=> + bool(true) +} +initial TypeError +array(2) { + ["dynamic"]=> + bool(true) + ["preserved"]=> + bool(true) +} +string(8) "detached" +write TypeError +array(1) { + ["replacement"]=> + bool(true) +} +array(1) { + ["private"]=> + bool(true) +} +array(3) { + [0]=> + string(6) "object" + [1]=> + string(8) "property" + [2]=> + string(6) "source" +} +array(1) { + ["ordered"]=> + bool(true) +} +bool(true) From 0821f62f20868c6598ecbab552ad0548765ab656 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 15:40:14 +0800 Subject: [PATCH 02/18] perf: benchmark release builds with O3 and LTO --- benchmark/README.md | 5 +++++ benchmark/property-access/project.yml | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/benchmark/README.md b/benchmark/README.md index a06ac89f..a0a14e32 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -15,3 +15,8 @@ Run the property benchmark from the repository root: ```bash php benchmark/property-access/run.php ``` + +The property benchmark builds the generated application with `-O3` and LTO. +For meaningful results, link it against a Release build of PHPX as well; a +Debug/`-O0` `libphpx` makes property helper calls several times slower and is +not representative of a release package. diff --git a/benchmark/property-access/project.yml b/benchmark/property-access/project.yml index 04ced7aa..001d17ba 100644 --- a/benchmark/property-access/project.yml +++ b/benchmark/property-access/project.yml @@ -1,6 +1,7 @@ name: property_access_benchmark mode: bin -optimize: 2 +optimize: 3 +lto: true build-dir: build output: property_access sources: From a4dee96fcf2683d586a409526b73f5fb3a74710b Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 16:13:11 +0800 Subject: [PATCH 03/18] perf: optimize stable integer property sums --- benchmark/property-access/README.md | 5 +- benchmark/property-access/run.php | 30 +++++- src/CompilerBase.php | 9 ++ src/Parser/BinaryOpTrait.php | 101 +++++++++++++++++- .../operator/runtime-int-overflow-return.phpt | 58 ++++++++++ .../final-int-property-add-chain.phpt | 48 +++++++++ 6 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 tests/compiler/operator/runtime-int-overflow-return.phpt create mode 100644 tests/compiler/optimizations/final-int-property-add-chain.phpt diff --git a/benchmark/property-access/README.md b/benchmark/property-access/README.md index 64e26f2e..488dc99e 100644 --- a/benchmark/property-access/README.md +++ b/benchmark/property-access/README.md @@ -1,8 +1,9 @@ # Dynamic property benchmark This benchmark compares the same dynamic and static property operations under -Zend PHP and a TypePHP `-O2` binary. Each metric is the best of seven rounds -after three warm-up rounds and is reported in nanoseconds per property access. +Zend PHP and a TypePHP `-O3` + LTO binary. Each metric is the best of seven +rounds after three warm-up rounds and is reported in nanoseconds per property +access. Run it from the repository root: diff --git a/benchmark/property-access/run.php b/benchmark/property-access/run.php index ce08f0e1..5776accd 100644 --- a/benchmark/property-access/run.php +++ b/benchmark/property-access/run.php @@ -14,12 +14,22 @@ foreach ($argv as $argument) { } } -/** @param list $command */ -function runCommand(array $command, string $cwd, bool $capture): string +/** + * @param list $command + * @param array|null $environment + */ +function runCommand(array $command, string $cwd, bool $capture, ?array $environment = null): string { $stdout = $capture ? ['pipe', 'w'] : STDOUT; $stderr = $capture ? ['pipe', 'w'] : STDERR; - $process = proc_open($command, [STDIN, $stdout, $stderr], $pipes, $cwd, null, ['bypass_shell' => true]); + $process = proc_open( + $command, + [STDIN, $stdout, $stderr], + $pipes, + $cwd, + $environment, + ['bypass_shell' => true], + ); if (!is_resource($process)) { throw new RuntimeException('Failed to start: ' . implode(' ', $command)); } @@ -79,7 +89,19 @@ $php = parseResults(runCommand([ '-r', 'require ' . var_export($source, true) . '; main();', ], $root, true)); -$typephp = parseResults(runCommand([$binary], $root, true)); +$typephpEnvironment = null; +if (PHP_OS_FAMILY !== 'Windows') { + $phpxHome = getenv('PHPX_HOME'); + if (!is_string($phpxHome) || $phpxHome === '') { + $phpxHome = $root . '/vendor/swoole/phpx'; + } + $typephpEnvironment = getenv(); + $loaderVariable = PHP_OS_FAMILY === 'Darwin' ? 'DYLD_LIBRARY_PATH' : 'LD_LIBRARY_PATH'; + $existingPath = $typephpEnvironment[$loaderVariable] ?? ''; + $typephpEnvironment[$loaderVariable] = $phpxHome . '/lib' + . ($existingPath === '' ? '' : PATH_SEPARATOR . $existingPath); +} +$typephp = parseResults(runCommand([$binary], $root, true, $typephpEnvironment)); echo "Metric PHP ns/op TypePHP ns/op TypePHP/PHP\n"; echo "------------------------------------------------------------\n"; diff --git a/src/CompilerBase.php b/src/CompilerBase.php index b8a36550..94a2e947 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -2390,6 +2390,15 @@ class CompilerBase implements PropertyAccessContext } // 实际函数的返回值 $type = $this->detectTypeOfExpr($v->expr); + // In ordinary PHP mode, int +/−/* int is only conditionally an int: + // runtime overflow promotes the result to float. Keep the Variant + // representation through the return boundary so a declared scalar + // return type observes and rejects that float exactly as PHP does. + // `use native_types` intentionally opts into native C++ arithmetic + // semantics and is therefore excluded from this check. + if (!$this->nativeTypes && $type === Type::INT && $this->exprCanOverflowInt($v->expr)) { + $type = Type::VAR; + } $nativeExpressionClass = $this->detectClassOfExpr($v->expr); if ($this->context->inClosure && $this->isNativeObjectClass($nativeExpressionClass)) { $this->fatalError($v, 'Zend closures cannot return native objects'); diff --git a/src/Parser/BinaryOpTrait.php b/src/Parser/BinaryOpTrait.php index 804cfb2e..3212cb40 100644 --- a/src/Parser/BinaryOpTrait.php +++ b/src/Parser/BinaryOpTrait.php @@ -15,6 +15,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp; use PhpParser\NodeAbstract; +use PhpParser\Modifiers; trait BinaryOpTrait { @@ -154,6 +155,21 @@ trait BinaryOpTrait return $folded; } + // Declared int parameters use the native Int ABI even in ordinary PHP + // mode. A direct C++ +/−/* would therefore have undefined signed + // overflow, while PHP promotes the result to float. Route dynamic + // integer arithmetic through the encapsulated Variant operators unless + // the user explicitly selected `use native_types`. Fully constant + // expressions remain safe to emit directly after the checks above. + if (!$this->nativeTypes + && $leftType === Type::INT + && $rightType === Type::INT + && in_array($op, ['+', '-', '*'], true) + && $this->evaluateConstantIntArithmetic($left, $right, $op) === null + ) { + return '((php::Var(' . $leftExpr . ')) ' . $op . ' (php::Var(' . $rightExpr . ')))'; + } + return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; } @@ -641,10 +657,93 @@ trait BinaryOpTrait protected function parseBinaryOpPlus(Expr\BinaryOp\Plus $expr): string { - return $this->parsePythonBinaryOperator($expr) + $python = $this->parsePythonBinaryOperator($expr); + if ($python !== null) { + return $python; + } + + return $this->tryParseFinalIntPropertyAddChain($expr) ?? $this->parseBinaryOp($expr->left, $expr->right, '+'); } + /** + * Lower a left-associated chain of stable declared-int property reads into + * one detached Variant accumulator. + * + * This keeps PHP overflow promotion and evaluation order in Variant's + * encapsulated operator+= while avoiding one owning temporary per binary + * AST node. The class/property must be final so a subclass cannot replace + * the declared property with a hook. Nullable, virtual and hooked + * properties stay on the general path. + */ + protected function tryParseFinalIntPropertyAddChain(Expr\BinaryOp\Plus $expr): ?string + { + if ($this->nativeTypes) { + return null; + } + + $operands = []; + $cursor = $expr; + while ($cursor instanceof Expr\BinaryOp\Plus) { + array_unshift($operands, $cursor->right); + $cursor = $cursor->left; + } + array_unshift($operands, $cursor); + + if (count($operands) < 3) { + return null; + } + + foreach ($operands as $operand) { + if (!$this->isStableFinalIntPropertyRead($operand)) { + return null; + } + } + + $accumulator = $this->addTmpVar(Type::VAR); + foreach ($operands as $index => $operand) { + /** @var Expr\PropertyFetch $operand */ + $value = $this->parsePropertyFetch($operand); + if ($index === 0) { + // Assignment into an already-declared Variant materializes an + // independent value. Do not use copy-initialization here: + // mandatory C++ copy elision could retain an Indirect alias. + $this->context->beforeStmtLines[] = $accumulator . ' = ' . $value . ';'; + } else { + $this->context->beforeStmtLines[] = $accumulator . ' += ' . $value . ';'; + } + } + + return $accumulator; + } + + protected function isStableFinalIntPropertyRead(NodeAbstract $operand): bool + { + if (!$operand instanceof Expr\PropertyFetch + || !$operand->var instanceof Expr\Variable + || !$this->isIdExpr($operand->name) + ) { + return false; + } + + $class = $this->resolveObjectClassDef($operand->var); + $propertyName = $this->parseIdentifier($operand->name); + if ($class === null || !$class->hasProperty($propertyName)) { + return false; + } + + $property = $class->getProperty($propertyName); + $stableDeclaration = ($class->flags & Modifiers::FINAL) !== 0 + || ($property->flags & Modifiers::FINAL) !== 0; + + return $stableDeclaration + && ($property->flags & Modifiers::STATIC) === 0 + && $property->type === Type::INT + && !$property->nullable + && !$property->virtual + && $property->getter === null; + } + protected function parseBinaryOpMul(Expr\BinaryOp\Mul $expr): string { return $this->parsePythonBinaryOperator($expr) diff --git a/tests/compiler/operator/runtime-int-overflow-return.phpt b/tests/compiler/operator/runtime-int-overflow-return.phpt new file mode 100644 index 00000000..55a5404c --- /dev/null +++ b/tests/compiler/operator/runtime-int-overflow-return.phpt @@ -0,0 +1,58 @@ +--TEST-- +Runtime integer overflow is checked at an int return boundary +--FILE-- +left + $this->right; + } +} + +function addInts(int $left, int $right): int +{ + return $left + $right; +} + +function subtractInts(int $left, int $right): int +{ + return $left - $right; +} + +function multiplyInts(int $left, int $right): int +{ + return $left * $right; +} + +function main(): void +{ + foreach ([ + static fn (): int => addInts(PHP_INT_MAX, 1), + static fn (): int => subtractInts(PHP_INT_MIN, 1), + static fn (): int => multiplyInts(PHP_INT_MAX, 2), + static function (): int { + $value = new OverflowProperties(); + $value->left = PHP_INT_MAX; + $value->right = 1; + return $value->sum(); + }, + ] as $callback) { + try { + var_dump($callback()); + } catch (TypeError $error) { + echo $error->getMessage(), "\n"; + } + } +} +?> +--EXPECTF-- +addInts(): Return value must be of type int, float returned +subtractInts(): Return value must be of type int, float returned +multiplyInts(): Return value must be of type int, float returned +OverflowProperties::sum(): Return value must be of type int, float returned diff --git a/tests/compiler/optimizations/final-int-property-add-chain.phpt b/tests/compiler/optimizations/final-int-property-add-chain.phpt new file mode 100644 index 00000000..2371507e --- /dev/null +++ b/tests/compiler/optimizations/final-int-property-add-chain.phpt @@ -0,0 +1,48 @@ +--TEST-- +Final int property addition uses a detached value accumulator +--FILE-- +first + $this->second + $this->third + $this->fourth + $this->fifth; + } +} + +function main(): void +{ + $value = new AddChain(); + $first =& $value->first; + + var_dump($value->sum()); + var_dump($value->first, $first); + + $value->first = PHP_INT_MAX; + $value->second = 1; + $value->third = 0; + $value->fourth = 0; + $value->fifth = 0; + try { + var_dump($value->sum()); + } catch (TypeError $error) { + echo $error->getMessage(), "\n"; + } + var_dump($value->first, $first); +} +?> +--EXPECTF-- +int(15) +int(1) +int(1) +AddChain::sum(): Return value must be of type int, float returned +int(9223372036854775807) +int(9223372036854775807) From 8b1c8e7d2670b4cb39af0fee3fff0556cac52633 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 16:41:56 +0800 Subject: [PATCH 04/18] perf: streamline dynamic property writes --- src/Parser/AssignOpTrait.php | 20 ++++++- src/Parser/ForeachTrait.php | 2 +- src/Parser/PropertyAccessTrait.php | 2 +- .../dynamic-property-write-fast-path.phpt | 60 +++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/compiler/object_property/dynamic-property-write-fast-path.phpt diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 63dd112e..d0aca8a6 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -102,7 +102,12 @@ trait AssignOpTrait return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')'; } - protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right, ?PropertyWriteTarget $target = null): string + protected function parseAssignPropertyFetch( + NodeAbstract $left, + NodeAbstract $right, + ?PropertyWriteTarget $target = null, + bool $resultUnused = false, + ): string { if ($target !== null) { $this->assertCanAssignPropertyWrite($target, $right); @@ -115,6 +120,15 @@ trait AssignOpTrait $rightExpr = $this->wrapObjectPropertyAssignTypeCheck($left, $right, $rightExpr); } + if ($resultUnused + && $left instanceof Expr\PropertyFetch + && !$this->shouldMaterializeOrderedOperand($left->name) + && $this->canEmitDirectArrayWriteOperand($right) + && $this->canEmitDynamicPropertyTarget($target) + ) { + return $this->emitDynamicPropertyFetchWrite($left, $rightExpr, $target); + } + $tmp = $this->genTmpVarName(); $this->addLocalVar($tmp, Type::VAR); // Comma expression: store RHS → execute side effect → evaluate to stored value @@ -494,7 +508,7 @@ trait AssignOpTrait } if ($propertyWriteTarget !== null && $this->shouldUseDynamicNativePropertyWrite($left, $type)) { - return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget); + return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget, $resultUnused); } if ($this->isVarExpr($left)) { @@ -645,7 +659,7 @@ trait AssignOpTrait } } } elseif ($this->isPropertyFetch($left) and !$this->isNativePropertyAccess($left)) { - return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget); + return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget, $resultUnused); } elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) { $tmp = $this->parseIdentifier($left->var); if ($this->getVarType($tmp) === Type::STR and $left->dim === null) { diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index 54f5478d..edd16e3c 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -124,7 +124,7 @@ trait ForeachTrait { $iterator = $this->genTmpVarName(); $byRef = $node->byRef ? 'true' : 'false'; - $scope = $this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'; + $scope = $this->class ? $this->getLocalClassEntryPtr($this->getFullClassName()) : 'nullptr'; $code = '{' . PHP_EOL; $this->indentLevel++; $code .= $this->getIndent() . "php::ForeachIterator $iterator{{$iterableVar}, $byRef, $scope};" . PHP_EOL; diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index a73a615c..f821b092 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -41,7 +41,7 @@ trait PropertyAccessTrait { $scope = $this->usesTraitPropertyScope($object) ? 'php::FakeScopeGuard::current()' - : ($this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'); + : ($this->class ? $this->getLocalClassEntryPtr($this->getFullClassName()) : 'nullptr'); return 'typephp_write_property_scoped(' . $object . ', ' . $property . ', ' . $value . ', ' . $scope . ')'; } diff --git a/tests/compiler/object_property/dynamic-property-write-fast-path.phpt b/tests/compiler/object_property/dynamic-property-write-fast-path.phpt new file mode 100644 index 00000000..ad08be30 --- /dev/null +++ b/tests/compiler/object_property/dynamic-property-write-fast-path.phpt @@ -0,0 +1,60 @@ +--TEST-- +Dynamic property statement writes preserve scope, evaluation and reference value semantics +--FILE-- +$name = $value; + } + + public function writeFromReference(string $name, mixed &$value): void + { + $this->$name = $value; + } + + public function writeComputed(string $name, int &$calls): void + { + $this->$name = nextDynamicValue($calls); + } + + public function value(): int + { + return $this->hidden; + } +} + +function nextDynamicValue(int &$calls): int +{ + $calls++; + return 41; +} + +function main(): void +{ + $writer = new DynamicWriter(); + $name = 'hidden'; + $writer->write($name, 17); + var_dump($writer->value()); + + $calls = 0; + $writer->writeComputed($name, $calls); + var_dump($writer->value(), $calls); + + $source = 42; + $writer->writeFromReference($name, $source); + $source = 43; + var_dump($writer->value(), $source); +} +?> +--EXPECT-- +int(17) +int(41) +int(1) +int(42) +int(43) From f6394934c5ed18e85b3dfe3eb6749ec2698029fa Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 16:56:19 +0800 Subject: [PATCH 05/18] fix: preserve imported enum cases in attribute arguments --- .../RuntimeAttributeFactoryLowering.php | 47 +++++----- .../attribute/imported-enum-defaults.phpt | 87 +++++++++++++++++++ 2 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 tests/compiler/attribute/imported-enum-defaults.phpt diff --git a/src/Transform/RuntimeAttributeFactoryLowering.php b/src/Transform/RuntimeAttributeFactoryLowering.php index ae8b15a4..0a884da2 100644 --- a/src/Transform/RuntimeAttributeFactoryLowering.php +++ b/src/Transform/RuntimeAttributeFactoryLowering.php @@ -78,14 +78,36 @@ final class RuntimeAttributeFactoryLowering extends NodeVisitorAbstract return null; } - if (!$node instanceof Node\Attribute) { - return null; + return null; + } + + public function leaveNode(Node $node): null + { + if ($node instanceof Node\Attribute) { + $this->lowerAttribute($node); + } elseif ($node instanceof Stmt\ClassLike) { + array_pop($this->classStack); + } elseif ($node instanceof Stmt\Namespace_) { + $factories = array_pop($this->namespaceFactories); + if ($factories !== []) { + array_push($node->stmts, ...$factories); + } + $this->namespace = ''; } - if (CompileTimeAttributeRegistry::get($node->name->toString()) !== null) { - return null; + return null; + } + + private function lowerAttribute(Node\Attribute $attribute): void + { + if (CompileTimeAttributeRegistry::get($attribute->name->toString()) !== null) { + return; } - foreach ($node->args as $argument) { + // Attribute children have now passed through NameResolver. Processing + // on enterNode() resolves imported enum names relative to the current + // namespace (for example `use A\\Status; Status::Active`) and misses + // the enum case, causing gen_stub to persist its backing scalar. + foreach ($attribute->args as $argument) { if (!$this->requiresFactory($argument->value)) { continue; } @@ -102,21 +124,6 @@ final class RuntimeAttributeFactoryLowering extends NodeVisitorAbstract $this->globalFactories[] = $factory['node']; } } - return null; - } - - public function leaveNode(Node $node): null - { - if ($node instanceof Stmt\ClassLike) { - array_pop($this->classStack); - } elseif ($node instanceof Stmt\Namespace_) { - $factories = array_pop($this->namespaceFactories); - if ($factories !== []) { - array_push($node->stmts, ...$factories); - } - $this->namespace = ''; - } - return null; } public function afterTraverse(array $nodes): ?array diff --git a/tests/compiler/attribute/imported-enum-defaults.phpt b/tests/compiler/attribute/imported-enum-defaults.phpt new file mode 100644 index 00000000..57c72aa3 --- /dev/null +++ b/tests/compiler/attribute/imported-enum-defaults.phpt @@ -0,0 +1,87 @@ +--TEST-- +Imported enum cases are preserved in property and parameter defaults +--FILE-- +explicit === IdType::Assigned); + var_dump($record->default === IdType::Auto); + var_dump($record->promoted === IdType::Auto); + var_dump($record->select() === IdType::Auto); + + $reflection = new ReflectionClass(Record::class); + $explicit = $reflection->getProperty('explicit')->getAttributes(TableId::class)[0]; + var_dump($explicit->getArguments()[0] === IdType::Assigned); + var_dump($explicit->newInstance()->type === IdType::Assigned); + + $default = $reflection->getProperty('default')->getAttributes(TableId::class)[0]; + var_dump($default->getArguments()); + var_dump($default->newInstance()->type === IdType::Auto); + + $parameter = $reflection->getMethod('select')->getParameters()[0]; + var_dump($parameter->getDefaultValue() === IdType::Auto); + } +} +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +array(0) { +} +bool(true) +bool(true) From 61d53179d17eb77390126d7773ed1d2c1f6b2812 Mon Sep 17 00:00:00 2001 From: hafung <32428762+hafung@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:00:43 +0800 Subject: [PATCH 06/18] fix(optimizer): convert dynamic array_keys strict flag --- src/Optimizer/FuncCallOptimizer.php | 3 +- .../stdlib/array-keys-dynamic-arguments.phpt | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/compiler/stdlib/array-keys-dynamic-arguments.phpt diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index 73297492..afac037a 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -770,7 +770,8 @@ trait FuncCallOptimizer { $cnt = count($e->args); if ($cnt >= 3) { - return 'php::fn::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', ' . $this->getArg($e, 2) . ')'; + return 'php::fn::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', ' + . $this->resolveArg($e, 2, self::ARG_TYPE_BOOL) . ')'; } if ($cnt >= 2) { return 'php::fn::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', false)'; diff --git a/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt b/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt new file mode 100644 index 00000000..b89e4cbd --- /dev/null +++ b/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt @@ -0,0 +1,89 @@ +--TEST-- +array_keys optimized calls convert dynamic arguments and preserve evaluation order +--FILE-- + 1, 'string' => '1']; +} + +function arrayKeysDynamicFilter(array &$events): mixed +{ + $events[] = 'filter'; + return '1'; +} + +function main() +{ + $values = ['integer' => 1, 'string' => '1']; + + var_dump(array_keys($values)); + var_dump(array_keys($values, '1')); + var_dump(array_keys($values, '1', true)); + + $strict = true; + var_dump(array_keys($values, '1', $strict)); + + $options = new ArrayKeysOptions(); + var_dump(array_keys($values, '1', $options->strict)); + + $events = []; + var_dump(array_keys( + arrayKeysDynamicValues($events), + arrayKeysDynamicFilter($events), + arrayKeysDynamicStrict($events) + )); + var_dump($events); +} +?> +--EXPECT-- +array(2) { + [0]=> + string(7) "integer" + [1]=> + string(6) "string" +} +array(2) { + [0]=> + string(7) "integer" + [1]=> + string(6) "string" +} +array(1) { + [0]=> + string(6) "string" +} +array(1) { + [0]=> + string(6) "string" +} +array(1) { + [0]=> + string(6) "string" +} +array(1) { + [0]=> + string(6) "string" +} +array(3) { + [0]=> + string(5) "array" + [1]=> + string(6) "filter" + [2]=> + string(6) "strict" +} From 0f430e85a656d32015be5569df1094c5e894e2f5 Mon Sep 17 00:00:00 2001 From: hafung <32428762+hafung@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:47:16 +0800 Subject: [PATCH 07/18] fix(optimizer): preserve array_keys strict type errors --- src/Optimizer/FuncCallOptimizer.php | 5 +- .../stdlib/array-keys-dynamic-arguments.phpt | 52 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index afac037a..f78f2ff9 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -766,10 +766,13 @@ trait FuncCallOptimizer return 'php::fn::get_parent_class(' . $this->parseIdentifier($arg) . ')'; } - protected function genArrayKeys(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genArrayKeys(string $n, Node\Expr\FuncCall $e, array $c): string|false { $cnt = count($e->args); if ($cnt >= 3) { + if ($this->detectTypeOfExpr($e->args[2]->value) !== Type::BOOL) { + return false; + } return 'php::fn::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', ' . $this->resolveArg($e, 2, self::ARG_TYPE_BOOL) . ')'; } diff --git a/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt b/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt index b89e4cbd..eb156c36 100644 --- a/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt +++ b/tests/compiler/stdlib/array-keys-dynamic-arguments.phpt @@ -1,5 +1,5 @@ --TEST-- -array_keys optimized calls convert dynamic arguments and preserve evaluation order +array_keys optimized calls preserve dynamic arguments, strict types, and evaluation order --FILE-- 1, 'string' => '1']; @@ -48,6 +68,29 @@ function main() arrayKeysDynamicStrict($events) )); var_dump($events); + + var_dump(array_keys($values, '1', arrayKeysMixedBool())); + + try { + array_keys($values, '1', arrayKeysMixedInt()); + echo "mixed-int=missing TypeError\n"; + } catch (TypeError $error) { + echo "mixed-int=TypeError\n"; + } + + try { + array_keys($values, '1', arrayKeysMixedArray()); + echo "mixed-array=missing TypeError\n"; + } catch (TypeError $error) { + echo "mixed-array=TypeError\n"; + } + + try { + array_keys($values, '1', arrayKeysUnionInt()); + echo "union-int=missing TypeError\n"; + } catch (TypeError $error) { + echo "union-int=TypeError\n"; + } } ?> --EXPECT-- @@ -87,3 +130,10 @@ array(3) { [2]=> string(6) "strict" } +array(1) { + [0]=> + string(6) "string" +} +mixed-int=TypeError +mixed-array=TypeError +union-int=TypeError From 4438c5807d05d20bff51dd637ea30688ed5f9ad5 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 17:27:56 +0800 Subject: [PATCH 08/18] docs: clarify attribute factory name resolution timing --- src/Transform/RuntimeAttributeFactoryLowering.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Transform/RuntimeAttributeFactoryLowering.php b/src/Transform/RuntimeAttributeFactoryLowering.php index 0a884da2..11bd393e 100644 --- a/src/Transform/RuntimeAttributeFactoryLowering.php +++ b/src/Transform/RuntimeAttributeFactoryLowering.php @@ -278,9 +278,11 @@ final class RuntimeAttributeFactoryLowering extends NodeVisitorAbstract } } if ($node instanceof Node\Name) { - // Attribute factories are created while the outer - // traverser is entering the Attribute node, before its - // argument names have been visited by NameResolver. + // Attribute factories are created in leaveNode(), after + // their argument names have passed through NameResolver. + // The compiler keeps the original node and records its + // target in resolvedName, while the stub pipeline may + // replace it with a FullyQualified node directly. if ($node instanceof Node\Name\Relative) { $name = ltrim($this->namespace . '\\' . $node->toString(), '\\'); return new Node\Name\FullyQualified($name, $node->getAttributes()); From 7b05882c106d424d6918ecb2fd328bd1b7da91c5 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 17:54:45 +0800 Subject: [PATCH 09/18] chore(project): bump version to 0.6.7 - Updated swoole/phpx dependency from ~2.6.4 to ~2.6.6 - Bumped project version from 0.6.6 to 0.6.7 in project.yml - Updated file version from 0.6.6.1112 to 0.6.7.1112 in project.yml - Updated product version from 0.6.6 to 0.6.7 in project.yml - Updated VERSION constant from '0.6.6' to '0.6.7' in Translator.php --- composer.json | 2 +- project.yml | 6 +++--- src/Translator.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 3a617091..14d1ec94 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "marcj/topsort": "^2.0", "symfony/var-dumper": "^8.0", "symfony/yaml": "^8.0", - "swoole/phpx": "~2.6.4", + "swoole/phpx": "~2.6.6", "ajaxray/ansikit": "^0.3", "ext-dom": "*" }, diff --git a/project.yml b/project.yml index 1c219ac3..177b4b06 100644 --- a/project.yml +++ b/project.yml @@ -1,6 +1,6 @@ name: tpc build-mode: bin -version: 0.6.6 +version: 0.6.7 cxx-std: c++17 cxx-flags: - -Wall @@ -12,8 +12,8 @@ resource: icon: swoole-logo.ico # 版本信息 version-info: - file-version: 0.6.6.1112 - product-version: 0.6.6 + file-version: 0.6.7.1112 + product-version: 0.6.7 company-name: "上海识沃网络科技有限公司" file-description: "TypePHP Compiler" internal-name: "typephp" diff --git a/src/Translator.php b/src/Translator.php index 0e807067..b894b7cb 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -68,7 +68,7 @@ class Translator extends Preprocessor use ResourceCompilationTrait; use ClassConstantValueTrait; - public const string VERSION = '0.6.6'; + public const string VERSION = '0.6.7'; public const string APP_NAME = 'TypePHP Compiler (AOT)'; protected bool $hasExplicitOutput = false; From 2ef53e7eeb0d2c03d8d19b0b255027b091d96428 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 18:04:32 +0800 Subject: [PATCH 10/18] rm composer.lock --- composer.lock | 4933 ------------------------------------------------- 1 file changed, 4933 deletions(-) delete mode 100644 composer.lock diff --git a/composer.lock b/composer.lock deleted file mode 100644 index c8ff010e..00000000 --- a/composer.lock +++ /dev/null @@ -1,4933 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "2d4aeff65ded28c1339be3c4f55f55eb", - "packages": [ - { - "name": "ajaxray/ansikit", - "version": "v0.3.1", - "source": { - "type": "git", - "url": "https://github.com/ajaxray/AnsiKit.git", - "reference": "b318313e879a248bf0fe77c792a3a03f232484f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ajaxray/AnsiKit/zipball/b318313e879a248bf0fe77c792a3a03f232484f0", - "reference": "b318313e879a248bf0fe77c792a3a03f232484f0", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ajaxray\\AnsiKit\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anis Uddin Ahmad", - "email": "anis.programmer@gmail.com" - } - ], - "description": "Tiny ANSI escape helper for terminal UIs (text styles, foreground & background colors, cursor positions, clearing) with table, progressbar, and more helpers.", - "support": { - "issues": "https://github.com/ajaxray/AnsiKit/issues", - "source": "https://github.com/ajaxray/AnsiKit/tree/v0.3.1" - }, - "time": "2025-09-22T13:57:26+00:00" - }, - { - "name": "league/climate", - "version": "3.11.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/climate.git", - "reference": "10622dc19e28d3376c82f18df2f07f6a41d20475" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/climate/zipball/10622dc19e28d3376c82f18df2f07f6a41d20475", - "reference": "10622dc19e28d3376c82f18df2f07f6a41d20475", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "seld/cli-prompt": "^1.0" - }, - "require-dev": { - "mikey179/vfsstream": "^1.6.12", - "mockery/mockery": "^1.6.12", - "phpunit/phpunit": "^9.6.21", - "squizlabs/php_codesniffer": "^4.0" - }, - "suggest": { - "ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\CLImate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joe Tannenbaum", - "email": "hey@joe.codes", - "homepage": "http://joe.codes/", - "role": "Developer" - }, - { - "name": "Craig Duncan", - "email": "git@duncanc.co.uk", - "homepage": "https://github.com/duncan3dc", - "role": "Developer" - } - ], - "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", - "keywords": [ - "cli", - "colors", - "command", - "php", - "terminal" - ], - "support": { - "issues": "https://github.com/thephpleague/climate/issues", - "source": "https://github.com/thephpleague/climate/tree/3.11.1" - }, - "time": "2026-07-25T09:31:15+00:00" - }, - { - "name": "marcj/topsort", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/marcj/topsort.php.git", - "reference": "972f58e42b5f110a0a1d8433247f65248abcfd5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/marcj/topsort.php/zipball/972f58e42b5f110a0a1d8433247f65248abcfd5c", - "reference": "972f58e42b5f110a0a1d8433247f65248abcfd5c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "codeclimate/php-test-reporter": "dev-master", - "phpunit/phpunit": "^9", - "symfony/console": "~2.5 || ~3.0 || ~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "MJS\\TopSort\\": "src/", - "MJS\\TopSort\\Tests\\": "tests/Tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marc J. Schmidt", - "email": "marc@marcjschmidt.de" - } - ], - "description": "High-Performance TopSort/Dependency resolving algorithm", - "keywords": [ - "dependency resolving", - "topological sort", - "topsort" - ], - "support": { - "issues": "https://github.com/marcj/topsort.php/issues", - "source": "https://github.com/marcj/topsort.php/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/marcj", - "type": "github" - } - ], - "time": "2020-09-24T12:39:55+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.6.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" - }, - "time": "2025-08-13T20:13:15+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "seld/cli-prompt", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/cli-prompt.git", - "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/b8dfcf02094b8c03b40322c229493bb2884423c5", - "reference": "b8dfcf02094b8c03b40322c229493bb2884423c5", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.63" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\CliPrompt\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", - "keywords": [ - "cli", - "console", - "hidden", - "input", - "prompt" - ], - "support": { - "issues": "https://github.com/Seldaek/cli-prompt/issues", - "source": "https://github.com/Seldaek/cli-prompt/tree/1.0.4" - }, - "time": "2020-12-15T21:32:01+00:00" - }, - { - "name": "swoole/phpx", - "version": "v2.6.4", - "source": { - "type": "git", - "url": "https://github.com/swoole/phpx.git", - "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swoole/phpx/zipball/87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd", - "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd", - "shasum": "" - }, - "require": { - "league/climate": "^3.10", - "marcj/topsort": "^2.0", - "php": ">=8.4 <8.6" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.75", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Phpx\\": "src/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "C++ wrapper for Zend API", - "keywords": [ - "ZendAPI", - "embedded", - "extension", - "php" - ], - "support": { - "issues": "https://github.com/swoole/phpx/issues", - "source": "https://github.com/swoole/phpx/tree/v2.6.4" - }, - "time": "2026-08-25T08:02:28+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-27T06:59:30+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "865103cf742a039f34645b971fc3ace308d6c167" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/865103cf742a039f34645b971fc3ace308d6c167", - "reference": "865103cf742a039f34645b971fc3ace308d6c167", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "symfony/console": "<7.4", - "symfony/error-handler": "<7.4" - }, - "require-dev": { - "symfony/console": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", - "twig/twig": "^3.12|^4.0" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-22T15:42:13+00:00" - }, - { - "name": "symfony/yaml", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "faabdbe998e8c5c599dceffa27aa265b185c0736" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/faabdbe998e8c5c599dceffa27aa265b185c0736", - "reference": "faabdbe998e8c5c599dceffa27aa265b185c0736", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<7.4" - }, - "require-dev": { - "symfony/console": "^7.4|^8.0", - "yaml/yaml-test-suite": "*" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-22T15:42:13+00:00" - } - ], - "packages-dev": [ - { - "name": "clue/ndjson-react", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/stream": "^1.2" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" - }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-12-23T10:58:28+00:00" - }, - { - "name": "composer/pcre", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", - "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<2.2.2" - }, - "require-dev": { - "phpstan/phpstan": "^2", - "phpstan/phpstan-deprecation-rules": "^2", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2026-06-07T11:47:49+00:00" - }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "ergebnis/agent-detector", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/ergebnis/agent-detector.git", - "reference": "e211f17928c8b95a51e06040792d57f5462fb271" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", - "reference": "e211f17928c8b95a51e06040792d57f5462fb271", - "shasum": "" - }, - "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.51.0", - "ergebnis/license": "^2.7.0", - "ergebnis/php-cs-fixer-config": "^6.60.2", - "ergebnis/phpstan-rules": "^2.13.1", - "ergebnis/phpunit-slow-test-detector": "^2.24.0", - "ergebnis/rector-rules": "^1.18.1", - "fakerphp/faker": "^1.24.1", - "infection/infection": "^0.26.6", - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.54", - "phpstan/phpstan-deprecation-rules": "^2.0.4", - "phpstan/phpstan-phpunit": "^2.0.16", - "phpstan/phpstan-strict-rules": "^2.0.10", - "phpunit/phpunit": "^9.6.34", - "rector/rector": "^2.4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.2-dev" - }, - "composer-normalize": { - "indent-size": 2, - "indent-style": "space" - } - }, - "autoload": { - "psr-4": { - "Ergebnis\\AgentDetector\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Andreas Möller", - "email": "am@localheinz.com", - "homepage": "https://localheinz.com" - } - ], - "description": "Provides a detector for detecting the presence of an agent.", - "homepage": "https://github.com/ergebnis/agent-detector", - "support": { - "issues": "https://github.com/ergebnis/agent-detector/issues", - "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", - "source": "https://github.com/ergebnis/agent-detector" - }, - "time": "2026-05-07T08:19:07+00:00" - }, - { - "name": "evenement/evenement", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Evenement\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" - }, - "time": "2023-08-08T05:53:35+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2025-08-14T07:29:31+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.18", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a8b4e4216faabf67f4e96110ee99a48c96e4e683" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8b4e4216faabf67f4e96110ee99a48c96e4e683", - "reference": "a8b4e4216faabf67f4e96110ee99a48c96e4e683", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.3", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.5", - "ergebnis/agent-detector": "^1.2", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.3", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.6", - "react/event-loop": "^1.5", - "react/socket": "^1.16", - "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", - "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.37", - "symfony/polyfill-php80": "^1.37", - "symfony/polyfill-php81": "^1.37", - "symfony/polyfill-php84": "^1.37", - "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", - "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.11.0", - "infection/infection": "^0.32.7", - "justinrainbow/json-schema": "^6.10.0", - "keradus/cli-executor": "^2.3", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.9.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", - "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31 || ^13.0.6", - "symfony/polyfill-php85": "^1.38", - "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.1", - "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.1" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/**/Internal/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.18" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2026-07-30T15:46:02+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "2.2.9", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", - "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ondřej Mirtes" - }, - { - "name": "Markus Staab" - }, - { - "name": "Vincent Langlet" - } - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2026-08-22T07:38:16+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:31:57+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.64", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", - "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-filter": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" - } - ], - "time": "2026-07-06T14:50:35+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "react/cache", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" - }, - { - "name": "react/child-process", - "version": "v0.6.7", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", - "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.7" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-12-23T15:25:20+00:00" - }, - { - "name": "react/dns", - "version": "v1.14.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", - "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.14.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-18T19:34:28+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", - "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-17T20:46:25+00:00" - }, - { - "name": "react/promise", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.12.28 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.3.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-08-19T18:57:03+00:00" - }, - { - "name": "react/socket", - "version": "v1.17.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", - "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.17.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-19T20:47:34+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:12:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:58:43+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:59:15+00:00" - }, - { - "name": "sebastian/comparator", - "version": "5.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:25:16+00:00" - }, - { - "name": "sebastian/complexity", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:37:17+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:15:17+00:00" - }, - { - "name": "sebastian/environment", - "version": "6.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-23T08:47:14+00:00" - }, - { - "name": "sebastian/exporter", - "version": "5.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:09:11+00:00" - }, - { - "name": "sebastian/global-state", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:19:19+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:38:20+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:08:32+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:06:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "5.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2025-08-10T07:50:56+00:00" - }, - { - "name": "sebastian/type", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:10:45+00:00" - }, - { - "name": "sebastian/version", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-07T11:34:05+00:00" - }, - { - "name": "symfony/console", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/535e18a1b8925f6c01a55b171d157ab66c2ace15", - "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php85": "^1.32", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4.6|^8.0.6" - }, - "conflict": { - "symfony/dependency-injection": "<8.1", - "symfony/event-dispatcher": "<8.1" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^8.1", - "symfony/event-dispatcher": "^8.1", - "symfony/filesystem": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/lock": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/stopwatch": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-27T13:58:19+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-05T06:23:12+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "c14c05a9e6da7f5e375e6efc28952c7e7dbddffb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c14c05a9e6da7f5e375e6efc28952c7e7dbddffb", - "reference": "c14c05a9e6da7f5e375e6efc28952c7e7dbddffb", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/security-http": "<7.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/error-handler": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", - "symfony/framework-bundle": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-22T15:42:13+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-05T06:23:12+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "17856b7a222664a26a5ea1cb06ee0721c2438217" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/17856b7a222664a26a5ea1cb06ee0721c2438217", - "reference": "17856b7a222664a26a5ea1cb06ee0721c2438217", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-22T15:42:13+00:00" - }, - { - "name": "symfony/finder", - "version": "v8.1.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e2989e762c70f9490fa3a00a0ac0fae5aa97a531", - "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531", - "shasum": "" - }, - "require": { - "php": ">=8.4.1" - }, - "require-dev": { - "symfony/filesystem": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v8.1.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-27T09:05:56+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-28T08:25:59+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-25T13:48:31+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", - "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T12:45:58+00:00" - }, - { - "name": "symfony/polyfill-php84", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T12:51:13+00:00" - }, - { - "name": "symfony/polyfill-php85", - "version": "v1.41.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", - "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-01T12:47:55+00:00" - }, - { - "name": "symfony/process", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "shasum": "" - }, - "require": { - "php": ">=8.4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-16T09:55:08+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "21c07b026905d596e8379caeb115d87aa479499d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", - "reference": "21c07b026905d596e8379caeb115d87aa479499d", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/service-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/string", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", - "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-intl-grapheme": "^1.33", - "symfony/polyfill-intl-normalizer": "^1.0", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/intl": "^7.4|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v8.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-28T07:35:25+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-11-17T20:03:58+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.4 <8.6", - "composer-runtime-api": "^2.2" - }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} From f2db98b7da5f0d2a43380e1d5378ff7ba47c77ee Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 18:36:57 +0800 Subject: [PATCH 11/18] fix: resolve php-config from selected PHP home --- phpunit/src/Platform/PlatformTest.php | 84 +++++++++++++++++++ src/Installer/LibPhpInstaller.php | 27 ++++-- src/Platform/UnixPlatform.php | 114 +++++++++++++++++++++++++- 3 files changed, 214 insertions(+), 11 deletions(-) diff --git a/phpunit/src/Platform/PlatformTest.php b/phpunit/src/Platform/PlatformTest.php index f499bb77..3106e85d 100644 --- a/phpunit/src/Platform/PlatformTest.php +++ b/phpunit/src/Platform/PlatformTest.php @@ -315,6 +315,90 @@ class PlatformTest extends TestCase $this->assertSame('', $platform->getCrtConfig()); } + public function testLinuxPhpHomeSelectsMatchingVersionedPhpConfig(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Unix php-config lookup test'); + } + + $root = sys_get_temp_dir() . '/typephp-php-home-' . bin2hex(random_bytes(6)); + $phpHome = $root . '/php'; + $rightInclude = $phpHome . '/include/right'; + $wrongInclude = $phpHome . '/include/wrong'; + mkdir($phpHome . '/bin', 0755, true); + mkdir($rightInclude, 0755, true); + mkdir($wrongInclude, 0755, true); + + $versionedConfig = $phpHome . '/bin/php-config' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + $wrongMinor = PHP_MINOR_VERSION === 4 ? 5 : 4; + $this->writePhpConfig($versionedConfig, PHP_VERSION, $phpHome, $rightInclude); + $this->writePhpConfig( + $phpHome . '/bin/php-config', + PHP_MAJOR_VERSION . '.' . $wrongMinor . '.0', + $phpHome, + $wrongInclude, + ); + + $previousPhpHome = getenv('PHP_HOME'); + try { + putenv('PHP_HOME=' . $phpHome); + $platform = new Linux(); + + $this->assertSame($phpHome, $platform->getPhpDir()); + $this->assertSame([$rightInclude], $platform->buildPhpIncludePaths($phpHome)); + } finally { + $previousPhpHome === false + ? putenv('PHP_HOME') + : putenv('PHP_HOME=' . $previousPhpHome); + unlink($versionedConfig); + unlink($phpHome . '/bin/php-config'); + rmdir($rightInclude); + rmdir($wrongInclude); + rmdir($phpHome . '/include'); + rmdir($phpHome . '/bin'); + rmdir($phpHome); + rmdir($root); + } + } + + public function testLinuxPhpHomeDoesNotFallBackToPathPhpConfig(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Unix php-config lookup test'); + } + + $root = sys_get_temp_dir() . '/typephp-php-home-missing-' . bin2hex(random_bytes(6)); + $phpHome = $root . '/php'; + mkdir($phpHome . '/bin', 0755, true); + $previousPhpHome = getenv('PHP_HOME'); + + try { + putenv('PHP_HOME=' . $phpHome); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('PHP_HOME does not provide an executable bin/php-config'); + (new Linux())->buildPhpIncludePaths($phpHome); + } finally { + $previousPhpHome === false + ? putenv('PHP_HOME') + : putenv('PHP_HOME=' . $previousPhpHome); + rmdir($phpHome . '/bin'); + rmdir($phpHome); + rmdir($root); + } + } + + private function writePhpConfig(string $path, string $version, string $prefix, string $include): 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", + escapeshellarg($version), + escapeshellarg($prefix), + escapeshellarg('-I' . $include), + ); + file_put_contents($path, $script); + chmod($path, 0755); + } + /** * 测试 macOS 平台基本功能 */ diff --git a/src/Installer/LibPhpInstaller.php b/src/Installer/LibPhpInstaller.php index cbe06e76..2f082bf4 100644 --- a/src/Installer/LibPhpInstaller.php +++ b/src/Installer/LibPhpInstaller.php @@ -176,13 +176,10 @@ final class LibPhpInstaller private function currentConfigureOptions(): string { - $phpConfig = $this->sourcePhpDir !== null && is_executable($this->sourcePhpDir . '/bin/php-config') - ? $this->sourcePhpDir . '/bin/php-config' - : trim((string) shell_exec('command -v php-config 2>/dev/null')); - if ($phpConfig !== '') { - return trim($this->capture([$phpConfig, '--configure-options'])); - } - + // 优先使用 PHP_BINARY -i 的 Configure Command:输出保留每个参数的 + // 引号,能正确处理 `CFLAGS=-g -O2` 这类含空格的值。而 + // `php-config --configure-options` 会丢失引号,导致含空格的值被 + // 错误拆分(例如 `-O2` 被当作独立参数传给 configure)。 $info = $this->capture([PHP_BINARY, '-n', '-i']); if (preg_match('/^Configure Command =>\s*(.+)$/mi', $info, $match)) { $words = PhpBuildConfiguration::parseShellWords(trim($match[1])); @@ -191,6 +188,22 @@ final class LibPhpInstaller } return implode(' ', array_map('escapeshellarg', $words)); } + + // 后备:php-config --configure-options。PPA 的多版本 PHP 共用 + // /usr 前缀,因此 PHP_HOME=/usr 时必须优先 php-config8.x。 + $versionedPhpConfig = $this->sourcePhpDir . '/bin/php-config' + . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + if ($this->sourcePhpDir !== null && is_executable($versionedPhpConfig)) { + $phpConfig = $versionedPhpConfig; + } elseif ($this->sourcePhpDir !== null && is_executable($this->sourcePhpDir . '/bin/php-config')) { + $phpConfig = $this->sourcePhpDir . '/bin/php-config'; + } else { + $phpConfig = trim((string) shell_exec('command -v php-config 2>/dev/null')); + } + if ($phpConfig !== '') { + return trim($this->capture([$phpConfig, '--configure-options'])); + } + throw new \RuntimeException('Unable to determine the current PHP configure options from php-config or php -i'); } diff --git a/src/Platform/UnixPlatform.php b/src/Platform/UnixPlatform.php index e2f101f5..9d7ef36b 100644 --- a/src/Platform/UnixPlatform.php +++ b/src/Platform/UnixPlatform.php @@ -99,8 +99,23 @@ abstract class UnixPlatform extends PlatformBase public function getPhpDir(): string { $phpDir = getenv('PHP_HOME'); - if ($phpDir && is_dir($phpDir)) { - return rtrim($phpDir, '\/'); + if (is_string($phpDir) && $phpDir !== '') { + $phpDir = rtrim($phpDir, '\/'); + if (!is_dir($phpDir)) { + throw new \RuntimeException("PHP_HOME is not a directory: {$phpDir}"); + } + return $phpDir; + } + + // Ubuntu/PPA 多版本环境下 php8.4 与 php-config8.4 并存,而 + // php-config 可能被 update-alternatives 指向其它版本。优先依据 + // PHP_BINARY 的版本后缀定位版本化 php-config,避免 ABI 错配。 + $versionedConfig = $this->findVersionedPhpConfig(dirname(realpath(PHP_BINARY) ?: PHP_BINARY)); + if ($versionedConfig !== null) { + $prefix = $this->getPhpConfigValue($versionedConfig, '--prefix'); + if ($prefix !== null && is_dir($prefix)) { + return rtrim($prefix, '/'); + } } // Composer executes tpc.php with an already selected PHP binary. Use @@ -198,24 +213,115 @@ abstract class UnixPlatform extends PlatformBase */ protected function findPhpConfig(string $phpDir): ?string { + $candidates = []; + $phpDir = rtrim($phpDir, '/'); + + $phpHome = getenv('PHP_HOME'); + if (is_string($phpHome) && $phpHome !== '') { + $phpHome = rtrim($phpHome, '/'); + $expected = realpath($phpHome) ?: $phpHome; + $actual = realpath($phpDir) ?: $phpDir; + if ($actual === $expected) { + // PHP_HOME is authoritative. Ubuntu/PPA installs several PHP + // versions under /usr, so prefer php-config8.x over the + // unversioned php-config selected by update-alternatives. + $versioned = $this->findVersionedPhpConfig($phpDir . '/bin'); + if ($versioned !== null) { + $candidates[] = $versioned; + } + $candidate = $phpDir . '/bin/php-config'; + if (is_executable($candidate)) { + $candidates[] = $candidate; + } + + if ($candidates === []) { + throw new \RuntimeException( + "PHP_HOME does not provide an executable bin/php-config: {$phpDir}" + ); + } + + foreach (array_unique($candidates) as $config) { + if ($this->phpConfigMatchesCurrentPhp($config)) { + return $config; + } + } + $this->reportPhpConfigVersionMismatch($candidates[0]); + } + } + + // Prefer the config belonging to the requested installation. If its + // unversioned config belongs to another PHP, the version check below + // will continue with the config beside the running PHP binary. $candidate = $phpDir . '/bin/php-config'; if (is_executable($candidate)) { - return $candidate; + $candidates[] = $candidate; + } + + $versioned = $this->findVersionedPhpConfig(dirname(realpath(PHP_BINARY) ?: PHP_BINARY)); + if ($versioned !== null) { + $candidates[] = $versioned; } + // 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)) { $prefix = $this->getPhpConfigValue($whichResult, '--prefix'); $expected = realpath($phpDir) ?: rtrim($phpDir, '/'); $actual = $prefix === null ? null : (realpath($prefix) ?: rtrim($prefix, '/')); if ($actual === $expected) { - return $whichResult; + $candidates[] = $whichResult; } } + // 依次返回第一个与当前 PHP 主次版本匹配的候选 + foreach (array_unique($candidates) as $config) { + if ($this->phpConfigMatchesCurrentPhp($config)) { + return $config; + } + } + + // 存在候选但版本均不匹配时给出明确错误 + if ($candidates !== []) { + $this->reportPhpConfigVersionMismatch($candidates[0]); + } + return null; } + /** Find php-config8.x for the PHP version executing the compiler. */ + private function findVersionedPhpConfig(string $binDir): ?string + { + $candidate = rtrim($binDir, '/') . '/php-config' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + return is_executable($candidate) ? $candidate : null; + } + + /** + * 校验 php-config 的主次版本号是否与当前运行的 PHP 一致。 + */ + private function phpConfigMatchesCurrentPhp(string $phpConfig): bool + { + $version = $this->getPhpConfigValue($phpConfig, '--version'); + if ($version === null) { + return false; + } + if (preg_match('/^(\d+\.\d+)\.\d+/', $version, $match)) { + return $match[1] === PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + } + return false; + } + + private function reportPhpConfigVersionMismatch(string $phpConfig): void + { + $version = $this->getPhpConfigValue($phpConfig, '--version') ?? 'unknown'; + throw new \RuntimeException(sprintf( + "The `php-config` (%s) reports PHP %s, but the running PHP is %s. " . + 'Set PHP_HOME to the matching PHP installation.', + $phpConfig, + $version, + PHP_VERSION, + )); + } + protected function getPhpConfigValue(string $phpConfig, string $option): ?string { $value = shell_exec(escapeshellarg($phpConfig) . ' ' . escapeshellarg($option) . ' 2>/dev/null'); From d11637faf0577d5a65e9ac54a4b2bfaa1c7c1215 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:01:11 +0800 Subject: [PATCH 12/18] feat: support static by-reference variadics --- docs/en/INCOMPATIBLE_PHP_FEATURES.md | 10 +- docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md | 6 +- docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md | 4 +- phpunit/src/FunctionTest.php | 4 +- phpunit/src/NegativeCompatibilityTest.php | 73 +++++++----- src/Generator/CallArgumentGenerator.php | 29 +++-- src/Generator/ClosureGenerator.php | 59 +++++++--- src/Generator/TypeCheckGenerator.php | 9 +- src/Parser/TypeConversionTrait.php | 10 ++ src/Preprocessor.php | 10 +- src/Translator.php | 4 +- tests/compiler/SKIP_TESTS.md | 9 +- .../closure/by-reference-parameters.phpt | 45 +++++++ tests/compiler/ref/ref-closure-param.phpt | 25 ++-- .../compiler/variadic/by-reference-basic.phpt | 111 ++++++++++++++++++ .../by-reference-closure-callback.phpt | 49 ++++++++ .../by-reference-dynamic-explicit.phpt | 50 ++++++++ .../variadic/by-reference-inheritance.phpt | 39 ++++++ .../compiler/variadic/by-reference-types.phpt | 93 +++++++++++++++ .../variadic/by-reference-unpack.phpt | 99 ++++++++++++++++ 20 files changed, 646 insertions(+), 92 deletions(-) create mode 100644 tests/compiler/closure/by-reference-parameters.phpt create mode 100644 tests/compiler/variadic/by-reference-basic.phpt create mode 100644 tests/compiler/variadic/by-reference-closure-callback.phpt create mode 100644 tests/compiler/variadic/by-reference-dynamic-explicit.phpt create mode 100644 tests/compiler/variadic/by-reference-inheritance.phpt create mode 100644 tests/compiler/variadic/by-reference-types.phpt create mode 100644 tests/compiler/variadic/by-reference-unpack.phpt diff --git a/docs/en/INCOMPATIBLE_PHP_FEATURES.md b/docs/en/INCOMPATIBLE_PHP_FEATURES.md index e9d7819b..dbb7404e 100644 --- a/docs/en/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/en/INCOMPATIBLE_PHP_FEATURES.md @@ -61,7 +61,10 @@ incompatible with or more restrictive than standard PHP. - `__construct()` may not have a return value. - A parameter with a default value may not appear before a required parameter (PHP permits this legacy pattern but treats the former parameter as required). -- Variadic parameters by reference `&...$args` are not supported. +- By-reference variadic parameters `&...$args` are supported for ordinary + functions and methods whose signature is known at compile time, including + direct, named, and unpacked arguments. A by-reference variadic declaration on + a dynamic Closure is not supported. - Union, intersection, and nullable types are still represented as `mixed/any` in C++, but the static analysis phase uses known expression types to reject definitely incompatible arguments, return values, and property assignments @@ -88,7 +91,10 @@ incompatible with or more restrictive than standard PHP. functions, ordinary methods, and native direct calls with known signatures; do not mistakenly describe the compiler's internal cross-trait dynamic-dispatch limitation as "TypePHP does not support reference parameters". -- Closures and arrow functions do not support reference parameters. +- Closures and arrow functions support fixed by-reference parameters. Because a + Closure invocation is dynamically dispatched, the caller must still mark + reference arguments explicitly with `refval()` / `toRef()`; Zend callbacks + use the generated Closure arginfo automatically. - Reference assignment cannot create a reference from a complex static-property expression. - Calls whose argument signature cannot be determined at compile time — dynamic diff --git a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md index 4c0ffe57..97c19173 100644 --- a/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md +++ b/docs/en/PHP_INCOMPATIBILITY_CLASSIFICATION.md @@ -77,6 +77,8 @@ These items should be documented with the exact boundary. | Reserved keyword methods such as `toArray()` | Intentional Rule | Conversion keywords are resolved before ordinary object methods to keep conversion lowering static and predictable. | | Zero-initialized fixed typed property slots | Intentional Rule / Partial | Native fixed-layout slots use their type's zero value instead of preserving every Zend uninitialized-property transition. | | Structural mutation of `std` containers during `foreach` | Intentional Rule | Native C++ iterators may be invalidated by append, insertion, erase or whole-container replacement. TypePHP rejects these operations inside the active loop while allowing non-structural element updates. | +| Automatic reference inference for dynamic calls | Intentional Rule | A runtime callable may resolve to a function, method, or Closure unknown to the compiler. TypePHP does not mirror callable signatures at runtime; callers must use `refval()` / `toRef()` explicitly. | +| By-reference variadic parameters on dynamic Closures | Intentional Rule | Supporting `&...` here would require signature-aware runtime argument packing. Statically resolved ordinary functions and methods support `&...`; dynamic Closures do not. | ## Implementable but Currently Unsupported @@ -86,11 +88,7 @@ These items should be documented with the exact boundary. | Variable variables (`$$var`) | Pending | Add a function-local symbol table mirror for dynamic locals, and disable or synchronize native locals that escape into dynamic lookup. | | Closure or arrow function returning by reference | Pending | Closure metadata and wrappers must preserve return-by-reference and emit `ReturnRef`. | | PHP 8.5 closures in constants, parameter defaults or property defaults | Pending | Use context-aware runtime initializers: cache constants and property defaults per request, create parameter defaults per omitted call, and never place request-local zvals in persistent MINIT storage. | -| Closure and arrow function by-reference parameters | Pending | Closure arginfo must preserve by-reference parameters and call lowering must pass reference slots. | -| By-reference variadic parameters (`&...$args`) | Pending | Variadic storage must preserve references instead of copying values. | -| By-reference parameters with default values | Pending | Need PHP-compatible handling for omitted arguments using temporary default values while still binding references for passed arguments. | | Reference assignment from complex static property expressions | Pending | Static property reference targets need complete lowering and lifetime handling. | -| Dynamic calls automatically converting by-reference arguments | Pending | Runtime callable metadata or reflection can identify by-reference parameters and build reference arguments dynamically. | | Calls with unpack plus trailing named arguments staying native | Pending | Normalize and reorder call arguments in IR before native-call selection. | | Dynamic `parent::method()` name | Pending | Needs runtime parent method lookup with correct call scope. | | Private typed property access on cloned objects through variables | Pending / Partial | Requires a complete declaring-class-aware access resolver. | diff --git a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md index 983721aa..7f00ac71 100644 --- a/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md @@ -28,7 +28,7 @@ - 暂不支持 PHP 8.5 在全局常量、类常量、参数默认值或属性默认值中使用 `static function`;初始化表达式内嵌套的闭包同样会在编译期被拒绝。 - `__construct()` 不允许返回值。 - 参数默认值不允许出现在必填参数之前(`PHP`允许,但会直接丢弃此默认参数)。 -- 不支持引用可变参数 `&...$args`。 +- 已知编译期签名的普通函数和方法支持引用可变参数 `&...$args`,包括直接参数、命名参数和参数展开;动态 Closure 暂不支持声明引用可变参数。 - 联合类型、交叉类型、`nullable` 类型仍以 `mixed/any` 作为 C++ 表示,但静态阶段会利用已知表达式类型提前拒绝确定不兼容的参数、返回值和属性赋值;动态值仍保留运行时 type check。 - 局部变量类型一旦被静态推断为具体 native 类型,不支持在同一作用域内重新赋值为不兼容类型。 @@ -44,7 +44,7 @@ - `exit(message: $value)` 可作为 TypePHP named-argument 扩展使用;它与位置参数 `exit($value)` 进入同一退出路径。 - TypePHP 使用严格参数数量规则:非 variadic 函数不接受声明范围之外的额外参数;`func_get_args()` 不会隐式放宽签名。 - 已知签名的普通函数、普通方法和 native 直调支持引用参数及写回;不要把编译器内部跨 Trait 动态分派的限制误写成“TypePHP 不支持引用参数”。 -- 闭包和箭头函数不支持引用参数。 +- 闭包和箭头函数支持固定引用参数。Closure 调用属于动态分派,调用方仍须通过 `refval()` / `toRef()` 显式标记引用参数;由 Zend 发起 callback 时则会自动使用编译器生成的 Closure arginfo。 - 引用赋值不支持从复杂静态属性表达式建立引用。 - 动态调用、闭包调用等编译期无法确定参数签名的调用,不能自动转换引用参数;需要显式使用 `refval()` 或等价关键词方法 `toRef()`。 - `refval()` / `toRef()` 只接受变量、数组元素或对象属性。 diff --git a/phpunit/src/FunctionTest.php b/phpunit/src/FunctionTest.php index 85f03046..e7c1b2c6 100644 --- a/phpunit/src/FunctionTest.php +++ b/phpunit/src/FunctionTest.php @@ -120,12 +120,12 @@ class FunctionTest extends \BaseTest public function testClosureReferenceParameter() { - $this->exec('Closure cannot use reference parameter', 'closure-ref-param.php'); + $this->compile('closure-ref-param.php'); } public function testVariadicReferenceParameter() { - $this->exec('Variadic parameters cannot be passed by reference', 'variadic-ref-param.php'); + $this->compile('variadic-ref-param.php'); } public function testOptionalParameterBeforeRequiredParameter() diff --git a/phpunit/src/NegativeCompatibilityTest.php b/phpunit/src/NegativeCompatibilityTest.php index 5fdfc65a..6a69a1b5 100644 --- a/phpunit/src/NegativeCompatibilityTest.php +++ b/phpunit/src/NegativeCompatibilityTest.php @@ -193,81 +193,96 @@ function main(): void PHP, ]; - yield 'closure reference parameter' => [ + yield 'closure reference return' => [ 'convert', - 'Closure cannot use reference parameter', + 'Closure and arrow functions cannot return by reference', <<<'PHP' [ - 'convert', - 'Closure cannot use reference parameter', + yield 'property get hook reference return' => [ + 'prepare', + 'Property get hooks returning by reference are not supported', <<<'PHP' $value; // @diagnostic + public string $value { + &get => $this->value; // @diagnostic + } } PHP, ]; - yield 'closure reference return' => [ + yield 'arrow function reference return' => [ 'convert', 'Closure and arrow functions cannot return by reference', <<<'PHP' $value; // @diagnostic } PHP, ]; - yield 'property get hook reference return' => [ - 'prepare', - 'Property get hooks returning by reference are not supported', + yield 'dynamic Closure reference variadic parameter' => [ + 'convert', + 'By-reference variadic parameters are not supported on dynamic Closures', <<<'PHP' $this->value; // @diagnostic - } + $callback = static function (&...$values): void { // @diagnostic + }; } PHP, ]; - yield 'arrow function reference return' => [ + yield 'literal passed to reference variadic parameter' => [ 'convert', - 'Closure and arrow functions cannot return by reference', + 'The left value of assignment operation can only be variable, array item, object property, class static property', <<<'PHP' $value; // @diagnostic + collect(42); // @diagnostic } PHP, ]; - yield 'reference variadic parameter' => [ - 'prepare', - 'Variadic parameters cannot be passed by reference', + yield 'reference variadic override must preserve by-reference contract' => [ + 'convert', + 'Declaration of `BrokenIncrementer::increment()` must be compatible with `IncrementContract::increment()`', <<<'PHP' unpack && $this->isVarExpr($arg->value)) { + $argInfo = $functionDef->argInfoList[$variadicArgIndex]; + // A single unpacked by-value native array is already the ABI + // value. A by-reference variadic must still separate the source + // and turn every element into a reference before entering the + // callee, matching Zend's argument-unpacking semantics. + if (!$argInfo->byRef && $variadicArgCount === 1 && $arg->unpack && $this->isVarExpr($arg->value)) { $var = $this->parseIdentifier($arg->value); if ($this->getVarType($var) === Type::ARRAY) { $resolvedArgs[$variadicArgIndex] = $var; @@ -155,16 +158,19 @@ trait CallArgumentGenerator } $variadicVar ??= $this->addTmpVar(Type::ARRAY); - $argInfo = $functionDef->argInfoList[$variadicArgIndex]; if ($arg->unpack) { - $this->context->beforeStmtLines[] = $variadicVar . '.merge(' . $this->parseArrayArg($arg) . ');'; + $method = $argInfo->byRef ? 'mergeReferences' : 'merge'; + $this->context->beforeStmtLines[] = $variadicVar . '.' . $method + . '(' . $this->parseArrayArg($arg) . ');'; } elseif ($variadicName !== null) { $value = $this->getTypeConvertedArg($arg, $argInfo, $callableName, $variadicArgIndex); - $this->context->beforeStmtLines[] = $variadicVar . '.setValue(' + $method = $argInfo->byRef ? 'set' : 'setValue'; + $this->context->beforeStmtLines[] = $variadicVar . '.' . $method . '(' . $this->getLiteralString($variadicName) . ', ' . $value . ');'; } else { $value = $this->getTypeConvertedArg($arg, $argInfo, $callableName, $variadicArgIndex); - $this->context->beforeStmtLines[] = $variadicVar . '.appendValue(' . $value . ');'; + $method = $argInfo->byRef ? 'append' : 'appendValue'; + $this->context->beforeStmtLines[] = $variadicVar . '.' . $method . '(' . $value . ');'; } } @@ -175,6 +181,15 @@ trait CallArgumentGenerator } if ($variadicVar !== null) { $resolvedArgs[$variadicArgIndex] = $variadicVar; + if ($functionDef->argInfoList[$variadicArgIndex]->byRef) { + // The aggregation array owns the second reference to every + // caller slot. Release it after the full PHP statement and + // also during C++ exception unwinding into a PHP catch block. + $cleanupGuard = $this->genTmpVarName(); + $this->context->beforeStmtLines[] = 'php::ArrayCleanupGuard ' . $cleanupGuard + . '{' . $variadicVar . '};'; + $this->context->afterStmtLines[] = $cleanupGuard . '.cleanup();'; + } } ksort($resolvedArgs); return implode(', ', $resolvedArgs); diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index 3ed87898..99428229 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -37,15 +37,19 @@ trait ClosureGenerator ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'; } - $parameterNames = []; + $parameterDescriptors = []; foreach ($params as $param) { $name = is_string($param->var->name) ? $param->var->name : $this->unescapeVarName($this->parseIdentifier($param->var)); - $parameterNames[] = $this->genCharPtr($name, true); - } - return 'php::newClosure(' . $callback . ', ' . $uses . ', ' . $thisArg . ', ' . $scope - . ', { ' . implode(', ', $parameterNames) . ' })'; + $parameterDescriptors[] = 'php::ClosureParameter{' + . $this->genCharPtr($name, true) . ', ' + . $this->escapeBool($param->byRef) . ', ' + . $this->escapeBool($param->variadic) . ', ' + . $this->escapeBool(!$param->variadic && $param->default === null) . '}'; + } + return 'php::newClosureWithParameters(' . $callback . ', ' . $uses . ', ' . $thisArg . ', ' . $scope + . ', { ' . implode(', ', $parameterDescriptors) . ' })'; } protected function parseArrowFunction(Expr\ArrowFunction $expr): string @@ -56,9 +60,6 @@ trait ClosureGenerator $params = []; foreach ($expr->params as $i => $param) { - if ($param->byRef) { - $this->fatalError($expr, 'Closure cannot use reference parameter'); - } if ($param->var instanceof Variable) { $params[$param->var->name] = $i; } @@ -153,6 +154,14 @@ trait ClosureGenerator } elseif ($expr->byRef) { $this->fatalError($expr, 'Closure and arrow functions cannot return by reference'); } + foreach ($params as $param) { + if ($param->byRef && $param->variadic) { + $this->fatalError( + $param, + 'By-reference variadic parameters are not supported on dynamic Closures', + ); + } + } $tmpVar = $this->genTmpVarName(); $code = $this->getIndent() . @@ -190,9 +199,6 @@ trait ClosureGenerator $code .= $this->genParameterCountCheck($requiredArgCount, count($params), $hasVariadic); foreach ($params as $i => $param) { - if ($param->byRef) { - $this->fatalError($expr, 'Closure cannot use reference parameter'); - } $var = $this->parseIdentifier($param->var); $phpName = is_string($param->var->name) ? $param->var->name : $this->unescapeVarName($var); if ($param->variadic) { @@ -210,11 +216,20 @@ trait ClosureGenerator $code .= $this->genClosureParamTypeCheck($param, $var, $phpName, $i, true); continue; } - $argExpr = $param->default === null - ? 'php::getCallArg(' . $i . ')' - : 'php::getCallArg(' . $i . ', ' . $this->parseParamDefaultValue($param->default) . ')'; - $code .= $this->getIndent() . 'auto ' . $var . ' = ' . $argExpr . ';' . PHP_EOL; - $this->addArgument($var, Type::VAR); + if ($param->byRef) { + $argExpr = $param->default === null + ? 'php::getCallArgByRef(' . $i . ')' + : 'php::getCallArgByRef(' . $i . ', php::newReference(' + . $this->parseParamDefaultValue($param->default) . '))'; + $code .= $this->getIndent() . Type::REF . ' ' . $var . ' = ' . $argExpr . ';' . PHP_EOL; + $this->addArgument($var, Type::REF); + } else { + $argExpr = $param->default === null + ? 'php::getCallArg(' . $i . ')' + : 'php::getCallArg(' . $i . ', ' . $this->parseParamDefaultValue($param->default) . ')'; + $code .= $this->getIndent() . 'auto ' . $var . ' = ' . $argExpr . ';' . PHP_EOL; + $this->addArgument($var, Type::VAR); + } if (CompileTimeAttribute::consume($param, 'Immutable')) { $this->context->immutableVars[$var] = true; if ($this->immutableTypeNodeMayBeObject($param->type)) { @@ -469,11 +484,19 @@ trait ClosureGenerator private function genClosureParamTypeCheck(Node\Param $param, string $var, string $phpName, int $index, bool $variadic): string { - if (!$param->type instanceof NullableType && !$param->type instanceof UnionType && !$param->type instanceof IntersectionType) { + if (!$param->byRef + && !$param->type instanceof NullableType + && !$param->type instanceof UnionType + && !$param->type instanceof IntersectionType + ) { + return ''; + } + + if ($param->type === null) { return ''; } - $typeInfo = $this->buildTypeCheckFromNode($param->type); + $typeInfo = $this->buildTypeCheckFromNode($param->type, $param->byRef); if (empty($typeInfo['check'])) { return ''; } diff --git a/src/Generator/TypeCheckGenerator.php b/src/Generator/TypeCheckGenerator.php index b6e16df6..c99fbd62 100644 --- a/src/Generator/TypeCheckGenerator.php +++ b/src/Generator/TypeCheckGenerator.php @@ -111,7 +111,7 @@ trait TypeCheckGenerator return $code; } - protected function buildTypeCheckFromNode(NodeAbstract $typeNode): array + protected function buildTypeCheckFromNode(NodeAbstract $typeNode, bool $includeSimpleType = false): array { $check = []; $typeStr = $this->typeCheckNodeToString($typeNode); @@ -141,8 +141,11 @@ trait TypeCheckGenerator if (!empty($clause)) { $check[] = count($clause) === 1 ? $clause[0] : ['kind' => 'allOf', 'types' => $clause]; } - } else { - return ['check' => [], 'typeStr' => '']; + } elseif ($includeSimpleType) { + $clause = $this->buildTypeCheckClause($typeNode); + if (!empty($clause)) { + $check[] = count($clause) === 1 ? $clause[0] : ['kind' => 'allOf', 'types' => $clause]; + } } if (empty($check)) { diff --git a/src/Parser/TypeConversionTrait.php b/src/Parser/TypeConversionTrait.php index 62a86066..5e7da7fc 100644 --- a/src/Parser/TypeConversionTrait.php +++ b/src/Parser/TypeConversionTrait.php @@ -269,6 +269,16 @@ trait TypeConversionTrait if ($expr instanceof Node\Expr\ArrayDimFetch) { return $this->parseArrayDimFetchUpdate($expr) . '.toReference()'; } + if ($expr instanceof Node\Expr\PropertyFetch) { + // A normal property read may return a temporary zval. Turning that + // temporary into a reference loses the typed-property source and + // can later detach the wrong source during destruction. Bind the + // reference to the actual property slot instead. + return $this->emitDynamicPropertyFetchRef($expr, $expr); + } + if ($expr instanceof Node\Expr\StaticPropertyFetch) { + return $this->emitStaticPropertyFetchRef($expr, $expr); + } $var = $this->parseIdentifier($expr); if ($this->isVarExpr($expr) and $this->isNativeTypeVar($var)) { $this->context->localVars[$var] = Type::VAR; diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 2007a9db..f824e9a7 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -861,8 +861,6 @@ class Preprocessor extends CompilerBase if ($param->variadic) { if ($i !== $last) { $this->fatalError($param, 'Variadic parameters must be the last parameter'); - } elseif ($param->byRef) { - $this->fatalError($param, 'Variadic parameters cannot be passed by reference'); } } if ($param->default && $i < $lastRequiredIndex) { @@ -888,8 +886,12 @@ class Preprocessor extends CompilerBase if ($param->type === null || $param->type instanceof NullableType) { $argInfo->nullable = true; } - if ($param->type instanceof NullableType || $param->type instanceof UnionType || $param->type instanceof IntersectionType) { - $typeInfo = $this->buildTypeCheckFromNode($param->type); + if (($param->byRef && $param->type !== null) + || $param->type instanceof NullableType + || $param->type instanceof UnionType + || $param->type instanceof IntersectionType + ) { + $typeInfo = $this->buildTypeCheckFromNode($param->type, $param->byRef); if (!empty($typeInfo['check']) && !$this->isNativeObjectClass($argInfo->declaredClass)) { $argInfo->typeCheck = $typeInfo['check']; $argInfo->typeStr = $typeInfo['typeStr']; diff --git a/src/Translator.php b/src/Translator.php index b894b7cb..dac9b883 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -3722,7 +3722,9 @@ CODE; $cppCode .= $this->getIndent() . Type::ARRAY . ' ' . $var . ';' . PHP_EOL; $cppCode .= $this->getIndent() . 'for (uint32_t i = ' . $k . '; i < php::getCallArgNum(); i++) {' . PHP_EOL; $this->indentLevel++; - if ($this->isStrictScalarType($argInfo->type)) { + if ($argInfo->byRef) { + $cppCode .= $this->getIndent() . $var . '.append(php::getCallArgByRef(i));' . PHP_EOL; + } elseif ($this->isStrictScalarType($argInfo->type)) { $rawVar = 'raw_' . $var; $cppCode .= $this->getIndent() . Type::VAR . ' ' . $rawVar . ' = php::getCallArg(i);' . PHP_EOL; $cppCode .= $this->genStrictScalarParamCheck($argInfo, $rawVar, $displayName, 'i + 1'); diff --git a/tests/compiler/SKIP_TESTS.md b/tests/compiler/SKIP_TESTS.md index 80aa0dd1..0703cfca 100644 --- a/tests/compiler/SKIP_TESTS.md +++ b/tests/compiler/SKIP_TESTS.md @@ -31,17 +31,12 @@ - **Skip 信息**: `skip: not supported` - **详细说明**: 复杂的动态属性访问链不支持 -### 5. ref-closure-param.phpt -- **原因**: 引用参数闭包不支持 -- **Skip 信息**: `skip` -- **详细说明**: 闭包函数中使用引用参数的场景不支持 - -### 6. innerHTML 相关测试 +### 5. innerHTML 相关测试 - **原因**: innerHTML DOM 操作不支持 - **Skip 信息**: `skip innerHTML and DOM manipulation not supported in AOT` - **详细说明**: JavaScript 风格的 DOM 操作不是 PHP 原生功能 -### 7. 游离代码测试 +### 6. 游离代码测试 - **原因**: 全局可执行表达式不支持 - **Skip 信息**: `skip Free-floating code not allowed, must be in function/method` - **详细说明**: 所有可执行表达式必须在函数或类的方法中 diff --git a/tests/compiler/closure/by-reference-parameters.phpt b/tests/compiler/closure/by-reference-parameters.phpt new file mode 100644 index 00000000..a82a0c95 --- /dev/null +++ b/tests/compiler/closure/by-reference-parameters.phpt @@ -0,0 +1,45 @@ +--TEST-- +Dynamic Closures accept positional arguments explicitly marked with refval +--FILE-- + ++$value; + $number = 40; + var_dump($arrow(refval($number)), $number); + + $typed = static function (int &$value): void { + $value++; + }; + $typed(refval($number)); + var_dump($number); + + $invalid = any('not-an-int'); + try { + $typed(refval($invalid)); + } catch (TypeError $error) { + echo "typed reference rejected\n"; + } +} +?> +--EXPECT-- +string(6) "fixed!" +NULL +int(41) +int(41) +int(42) +typed reference rejected diff --git a/tests/compiler/ref/ref-closure-param.phpt b/tests/compiler/ref/ref-closure-param.phpt index 03c77079..0e37e70f 100644 --- a/tests/compiler/ref/ref-closure-param.phpt +++ b/tests/compiler/ref/ref-closure-param.phpt @@ -1,21 +1,10 @@ --TEST-- closure function with ref parameter ---SKIPIF-- - --FILE-- 'apple', 'b' => 'banana'); @@ -26,4 +15,14 @@ function main() } ?> --EXPECT-- -string(7) "foo bar" +array(2) { + ["sweet"]=> + array(2) { + ["a"]=> + string(9) "apple (_)" + ["b"]=> + string(10) "banana (_)" + } + ["sour"]=> + string(9) "lemon (_)" +} diff --git a/tests/compiler/variadic/by-reference-basic.phpt b/tests/compiler/variadic/by-reference-basic.phpt new file mode 100644 index 00000000..d764fa14 --- /dev/null +++ b/tests/compiler/variadic/by-reference-basic.phpt @@ -0,0 +1,111 @@ +--TEST-- +By-reference variadic parameters preserve direct, named and method arguments +--FILE-- +double($one, $two); + var_dump($one, $two); + + $array = [7]; + $target = new VariadicReferenceTarget(); + VariadicReferenceMutator::increment( + $array[0], + $target->value, + VariadicReferenceTarget::$staticValue, + ); + var_dump($array, $target->value, VariadicReferenceTarget::$staticValue); + + // As in PHP, passing an undefined variable by reference creates it. + VariadicReferenceMutator::increment($createdByReference); + var_dump($createdByReference); + + $parameter = (new ReflectionFunction('suffix'))->getParameters()[1]; + var_dump($parameter->isVariadic(), $parameter->isPassedByReference()); +} +?> +--EXPECT-- +array(0) { +} +array(2) { + [0]=> + int(0) + [1]=> + int(1) +} +string(6) "first!" +string(7) "second!" +array(2) { + [0]=> + string(4) "left" + [1]=> + string(5) "right" +} +string(5) "left?" +string(6) "right?" +int(2) +int(3) +int(4) +int(6) +array(1) { + [0]=> + int(8) +} +int(11) +int(21) +int(1) +bool(true) +bool(true) diff --git a/tests/compiler/variadic/by-reference-closure-callback.phpt b/tests/compiler/variadic/by-reference-closure-callback.phpt new file mode 100644 index 00000000..39b36795 --- /dev/null +++ b/tests/compiler/variadic/by-reference-closure-callback.phpt @@ -0,0 +1,49 @@ +--TEST-- +Reference Closure parameters work at Zend callback boundaries used by Symfony mbstring polyfill +--FILE-- + 'one', 'nested' => ['b' => 'two']]; + $second = 'three'; + var_dump(convert_values('!', $first, $second)); + var_dump($first, $second); + + $invalid = ['ok', 42]; + var_dump(convert_values('?', $invalid)); + var_dump($invalid); +} +?> +--EXPECT-- +bool(true) +array(2) { + ["a"]=> + string(6) "one!:a" + ["nested"]=> + array(1) { + ["b"]=> + string(6) "two!:b" + } +} +string(8) "three!:1" +bool(false) +array(2) { + [0]=> + string(5) "ok?:0" + [1]=> + int(42) +} diff --git a/tests/compiler/variadic/by-reference-dynamic-explicit.phpt b/tests/compiler/variadic/by-reference-dynamic-explicit.phpt new file mode 100644 index 00000000..fcad5efb --- /dev/null +++ b/tests/compiler/variadic/by-reference-dynamic-explicit.phpt @@ -0,0 +1,50 @@ +--TEST-- +Dynamic calls require explicit refval for by-reference arguments +--FILE-- + +--EXPECT-- +int(41) +string(4) "one!" +string(4) "two!" +string(5) "two!?" diff --git a/tests/compiler/variadic/by-reference-inheritance.phpt b/tests/compiler/variadic/by-reference-inheritance.phpt new file mode 100644 index 00000000..cde662bc --- /dev/null +++ b/tests/compiler/variadic/by-reference-inheritance.phpt @@ -0,0 +1,39 @@ +--TEST-- +By-reference variadic signatures remain compatible across interfaces and inheritance +--FILE-- +increment($first, $second); + var_dump($first, $second); +} +?> +--EXPECT-- +int(11) +int(21) diff --git a/tests/compiler/variadic/by-reference-types.phpt b/tests/compiler/variadic/by-reference-types.phpt new file mode 100644 index 00000000..d3ed129d --- /dev/null +++ b/tests/compiler/variadic/by-reference-types.phpt @@ -0,0 +1,93 @@ +--TEST-- +Typed by-reference variadics validate, widen float arguments and write through unions and objects +--FILE-- +value++; + } +} + +function require_ints(int &...$values): void +{ +} + +function main(): void +{ + $integer = 2; + $float = 2.5; + scale($integer, $float); + var_dump($integer, $float); + + $values = [4, 6.0]; + scale(...$values); + var_dump($values); + + $number = 10; + $text = 'hello'; + normalize($number, $text); + var_dump($number, $text); + + $first = new Counter(1); + $second = new Counter(5); + bump_objects($first, $second); + var_dump($first->value, $second->value); + + $invalid = any('not-an-int'); + try { + require_ints($invalid); + } catch (TypeError $error) { + echo get_class($error), ': ', $error->getMessage(), PHP_EOL; + } + + $invalidUnpack = ['still-not-an-int']; + try { + require_ints(...$invalidUnpack); + } catch (TypeError $error) { + echo "unpack rejected\n"; + } + var_dump(ReflectionReference::fromArrayElement($invalidUnpack, 0)); +} +?> +--EXPECTF-- +float(3) +float(3.75) +array(2) { + [0]=> + float(6) + [1]=> + float(9) +} +int(11) +string(5) "HELLO" +int(2) +int(6) +TypeError: require_ints(): Argument #1 ($values) must be of type int, string given +unpack rejected +NULL diff --git a/tests/compiler/variadic/by-reference-unpack.phpt b/tests/compiler/variadic/by-reference-unpack.phpt new file mode 100644 index 00000000..d2775a1c --- /dev/null +++ b/tests/compiler/variadic/by-reference-unpack.phpt @@ -0,0 +1,99 @@ +--TEST-- +By-reference variadic unpack preserves writeback, COW separation, keys and existing references +--FILE-- + 10, 'right' => 20]; + var_dump(increment_all(...$named)); + var_dump($named); + + $first = [30]; + $second = [40, 50]; + var_dump(increment_all(...$first, ...$second)); + var_dump($first, $second); + + $external = 60; + $references = [&$external]; + increment_all(...$references); + var_dump($external, $references); + + // A temporary has no caller-visible slots, but remains a valid unpack. + var_dump(increment_all(...[70, 80])); +} +?> +--EXPECT-- +array(2) { + [0]=> + int(0) + [1]=> + int(1) +} +array(2) { + [0]=> + int(2) + [1]=> + int(3) +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +array(2) { + [0]=> + string(4) "left" + [1]=> + string(5) "right" +} +array(2) { + ["left"]=> + int(11) + ["right"]=> + int(21) +} +array(3) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) +} +array(1) { + [0]=> + int(31) +} +array(2) { + [0]=> + int(41) + [1]=> + int(51) +} +int(61) +array(1) { + [0]=> + &int(61) +} +array(2) { + [0]=> + int(0) + [1]=> + int(1) +} From cfd8e7ebbe83657e7be0f9f773445aa10fccf7a5 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:07:51 +0800 Subject: [PATCH 13/18] test: reduce recursive Fibonacci workload --- tests/compiler/basic/fib.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compiler/basic/fib.phpt b/tests/compiler/basic/fib.phpt index 29dce2ac..cd3fe3d2 100644 --- a/tests/compiler/basic/fib.phpt +++ b/tests/compiler/basic/fib.phpt @@ -13,7 +13,7 @@ function fib(int $n): int function main() { - $n = 40; + $n = 30; if ($n > 100) { echo "Too big number\n"; exit(1); @@ -27,4 +27,4 @@ function main() } ?> --EXPECT-- -102334155 \ No newline at end of file +832040 From a44fd22e50a6c3de9e0f2c0766e30cef1c69f924 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:17:44 +0800 Subject: [PATCH 14/18] chore(deps): update swoole/phpx dependency version - Updated swoole/phpx from ~2.6.6 to ~2.6.7 in composer.json - Bumped minor version for bug fixes and improvements - Maintained compatibility with existing codebase - Updated dependency constraint in require section --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 14d1ec94..29d32c09 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "marcj/topsort": "^2.0", "symfony/var-dumper": "^8.0", "symfony/yaml": "^8.0", - "swoole/phpx": "~2.6.6", + "swoole/phpx": "~2.6.7", "ajaxray/ansikit": "^0.3", "ext-dom": "*" }, From 9fd46c22e3ef429f3c81be25deda1aafebc3118d Mon Sep 17 00:00:00 2001 From: Alessio Giacobbe Date: Fri, 28 Aug 2026 11:55:16 +0200 Subject: [PATCH 15/18] fix(parser): propagate multi-level break/continue before trailing statements The flag checks that translate `break N` / `continue N` were emitted only at the end of each enclosing loop body. After the inner construct exited with the countdown flag set, every trailing statement of the enclosing body still executed before the check ran: foreach ([1] as $x) { foreach ([1] as $y) { break 2; } echo "leaked"; // ran in compiled output, not in PHP } The native (int-typed) switch path was worse: its check sat inside the do-while(0) wrapper, decrementing the flag a second time for the switch level the C++ `break` had already exited. A `break 2` from a native switch inside a loop therefore never exited the loop at all. Emit the propagation check immediately after every nested loop / switch statement instead, from the statement dispatcher, and drop the dead end-of-body emissions. The check now also distinguishes the enclosing construct: when it sits inside a switch, a continue that lands on the switch level lowers to `break`, matching PHP's continue-targets-switch semantics. parseBreak/parseContinue now reject levels exceeding the number of enclosing breakable constructs - the same compile-time validation PHP performs (`Cannot 'break' 2 levels`) - which the countdown scheme relies on to terminate at an enclosing construct. The continue-2-while scenario in break-continue-level.phpt encoded the old leaked behavior: its `$i++` after the inner loop only ran because of the misplaced check; standard PHP loops forever on it. The counter now advances before the inner loop. --- src/CompilerBase.php | 50 +++--- src/Context/FunctionContext.php | 4 + src/Parser/ForeachTrait.php | 2 +- src/Parser/LoopControlTrait.php | 39 ++++- src/Parser/SwitchTrait.php | 4 +- .../break-continue-level-placement.phpt | 155 ++++++++++++++++++ .../control_flow/break-continue-level.phpt | 7 +- 7 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 tests/compiler/control_flow/break-continue-level-placement.phpt diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 94a2e947..dce4d484 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -1687,6 +1687,8 @@ class CompilerBase implements PropertyAccessContext $lines = []; $inLoopTop = $this->context->inLoop; $inContinuableLoopTop = $this->context->inContinuableLoop; + $breakableIsSwitchTop = $this->context->breakableIsSwitch; + $breakableDepthTop = $this->context->breakableDepth; $last = array_key_last($stmts); foreach ($stmts as $i => $v) { $class = $v->getType(); @@ -1718,37 +1720,37 @@ class CompilerBase implements PropertyAccessContext $result = $this->parseReturn($v); break; case 'Stmt_For': - $this->context->inLoop = true; - $this->context->inContinuableLoop = true; - $result = $this->parseFor($v); - $this->context->inLoop = $inLoopTop; - $this->context->inContinuableLoop = $inContinuableLoopTop; - break; case 'Stmt_Foreach': - $this->context->inLoop = true; - $this->context->inContinuableLoop = true; - $result = $this->parseForeach($v); - $this->context->inLoop = $inLoopTop; - $this->context->inContinuableLoop = $inContinuableLoopTop; - break; case 'Stmt_Switch': - $this->context->inLoop = true; - $result = $this->parseSwitch($v); - $this->context->inLoop = $inLoopTop; - break; case 'Stmt_While': - $this->context->inLoop = true; - $this->context->inContinuableLoop = true; - $result = $this->parseWhile($v); - $this->context->inLoop = $inLoopTop; - $this->context->inContinuableLoop = $inContinuableLoopTop; - break; case 'Stmt_Do': + $isSwitch = $class === 'Stmt_Switch'; $this->context->inLoop = true; - $this->context->inContinuableLoop = true; - $result = $this->parseDo($v); + if (!$isSwitch) { + $this->context->inContinuableLoop = true; + } + $this->context->breakableIsSwitch = $isSwitch; + $this->context->breakableDepth = $breakableDepthTop + 1; + $result = match ($class) { + 'Stmt_For' => $this->parseFor($v), + 'Stmt_Foreach' => $this->parseForeach($v), + 'Stmt_Switch' => $this->parseSwitch($v), + 'Stmt_While' => $this->parseWhile($v), + default => $this->parseDo($v), + }; $this->context->inLoop = $inLoopTop; $this->context->inContinuableLoop = $inContinuableLoopTop; + $this->context->breakableIsSwitch = $breakableIsSwitchTop; + $this->context->breakableDepth = $breakableDepthTop; + // A multi-level break/continue exits the nested construct + // with its countdown flag still set. The propagation check + // must run before any trailing statement of this body. + if ($inLoopTop) { + $flagCheck = $this->genMultiLevelJumpCheck($breakableIsSwitchTop); + if ($flagCheck !== '') { + $result = rtrim($result, "\r\n") . PHP_EOL . $flagCheck; + } + } break; case 'Stmt_If': $result = $this->parseIf($v); diff --git a/src/Context/FunctionContext.php b/src/Context/FunctionContext.php index 9a0dda35..d07df058 100644 --- a/src/Context/FunctionContext.php +++ b/src/Context/FunctionContext.php @@ -84,6 +84,10 @@ class FunctionContext public bool $inLoop = false; /** True while parsing a for/foreach/while/do-while body. */ public bool $inContinuableLoop = false; + /** Number of breakable constructs (loops and switches) enclosing the statement being parsed. */ + public int $breakableDepth = 0; + /** True when the innermost enclosing breakable construct is a switch, not a loop. */ + public bool $breakableIsSwitch = false; public bool $inClosure = false; public ?array $closureReturnTypeCheck = null; public string $closureReturnTypeStr = ''; diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index edd16e3c..5fc77a9e 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -48,7 +48,7 @@ trait ForeachTrait protected function parseForeachBody(Foreach_ $node): string { - return $this->parseStmts($node->stmts) . $this->genLoopEndFlagCheck(); + return $this->parseStmts($node->stmts); } protected function parseForeachKeyAssignment(Foreach_ $node, string $keyExpr, string $defaultType = Type::VAR): string diff --git a/src/Parser/LoopControlTrait.php b/src/Parser/LoopControlTrait.php index 8cd9f48c..501cd2d0 100644 --- a/src/Parser/LoopControlTrait.php +++ b/src/Parser/LoopControlTrait.php @@ -102,7 +102,6 @@ trait LoopControlTrait $code .= ') {' . PHP_EOL; $code .= $this->parseBlockStmts($stmts); - $code .= $this->genLoopEndFlagCheck(); $code .= $this->getIndent() . '}' . PHP_EOL; return $code; @@ -138,7 +137,6 @@ trait LoopControlTrait $code .= 'while (' . $cond . ') {' . PHP_EOL; } $code .= $this->parseBlockStmts($stmts); - $code .= $this->genLoopEndFlagCheck(); $code .= $this->getIndent() . '}' . PHP_EOL; return $code; @@ -172,7 +170,6 @@ trait LoopControlTrait $code = $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'do {' . PHP_EOL; $code .= $bodyCode; - $code .= $this->genLoopEndFlagCheck(); $code .= $this->getIndent() . '} while (' . $cond . ');' . PHP_EOL; return $code; @@ -189,6 +186,7 @@ trait LoopControlTrait } $num = $v->num; if ($num) { + $this->checkLoopJumpLevel($v, $num, 'break'); if ($num->value > 1) { $this->context->hasMultiLevelBreak = true; return '_brk_flag = ' . ($num->value - 1) . '; break;'; @@ -205,6 +203,7 @@ trait LoopControlTrait } $num = $v->num; if ($num) { + $this->checkLoopJumpLevel($v, $num, 'continue'); if ($num->value > 1) { $this->context->hasMultiLevelContinue = true; return '_cnt_flag = ' . ($num->value - 1) . '; break;'; @@ -214,12 +213,32 @@ trait LoopControlTrait } /** - * Emit flag-propagation checks at the end of a loop body. + * PHP only accepts a positive integer literal that does not exceed the + * number of enclosing loops/switches. The flag lowering relies on this: + * it guarantees the countdown reaches zero at an enclosing construct. + */ + protected function checkLoopJumpLevel(Node\Stmt $v, Node\Expr $num, string $operator): void + { + if (!$num instanceof Node\Scalar\Int_ || $num->value < 1) { + $this->fatalError($v, "'{$operator}' operator accepts only positive integer literals"); + } + if ($num->value > $this->context->breakableDepth) { + $this->fatalError($v, "Cannot '{$operator}' {$num->value} levels"); + } + } + + /** + * Emit flag-propagation checks right after a nested breakable construct. * - * Translates multi-level break / continue into plain break / continue - * by decrementing a counter at each loop boundary until it reaches zero. + * A multi-level break / continue is lowered to a flag assignment plus a + * plain break out of the innermost construct. Each enclosing loop or + * switch places this check immediately after every nested loop / switch + * statement, so the flag keeps breaking outward — before any trailing + * statements of the enclosing body can run — until it reaches zero at + * the targeted level. When the check sits inside a switch, a continue + * that lands on the switch level behaves like break, matching PHP. */ - protected function genLoopEndFlagCheck(): string + protected function genMultiLevelJumpCheck(bool $enclosingIsSwitch): string { $code = ''; $indent = $this->getIndent(); @@ -227,7 +246,11 @@ trait LoopControlTrait $code .= "{$indent}if (_brk_flag > 0) { _brk_flag--; break; }" . PHP_EOL; } if ($this->context->hasMultiLevelContinue) { - $code .= "{$indent}if (_cnt_flag > 0) { _cnt_flag--; if (_cnt_flag == 0) continue; else break; }" . PHP_EOL; + if ($enclosingIsSwitch) { + $code .= "{$indent}if (_cnt_flag > 0) { _cnt_flag--; break; }" . PHP_EOL; + } else { + $code .= "{$indent}if (_cnt_flag > 0) { _cnt_flag--; if (_cnt_flag == 0) continue; else break; }" . PHP_EOL; + } } return $code; } diff --git a/src/Parser/SwitchTrait.php b/src/Parser/SwitchTrait.php index 08d3dedf..794391c1 100644 --- a/src/Parser/SwitchTrait.php +++ b/src/Parser/SwitchTrait.php @@ -2,7 +2,7 @@ /** * This file is part of TypePHP. * - * Lowers switch cases, fallthrough, defaults, and loop-exit flags. + * Lowers switch cases, fallthrough, and defaults. */ namespace TypePhp\Parser; @@ -64,7 +64,6 @@ trait SwitchTrait } $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; - $code .= $this->genLoopEndFlagCheck(); $this->indentLevel--; $code .= $this->getIndent() . '} while(0);' . PHP_EOL; @@ -166,7 +165,6 @@ trait SwitchTrait $code .= $this->getIndent() . '}' . PHP_EOL; } } - $code .= $this->genLoopEndFlagCheck(); $this->indentLevel--; $code .= $this->getIndent() . '} while (0);'; diff --git a/tests/compiler/control_flow/break-continue-level-placement.phpt b/tests/compiler/control_flow/break-continue-level-placement.phpt new file mode 100644 index 00000000..eaeac9a7 --- /dev/null +++ b/tests/compiler/control_flow/break-continue-level-placement.phpt @@ -0,0 +1,155 @@ +--TEST-- +Multi-level break/continue must skip trailing statements of enclosing bodies +--FILE-- + +--EXPECT-- +b2 inner 1.1 +break-2: done +c2 inner 1.1 +c2 inner 2.1 +continue-2: done +sw iter 0 +sw after 0 +sw iter 1 +sw case 1 +switch-break-2: done +swc after 0 +swc case 1 +swc after 2 +switch-continue-2: done +b3 deep 0.1 +break-3-through-switch: done +c2s deep 0.1 +c2s after switch 0 +c2s after switch 1 +continue-2-targets-switch: done +c3 deep 0.1 +c3 after switch 1 +continue-3-through-switch: done +n-iter 0 +n-case +native-switch-break-2: done diff --git a/tests/compiler/control_flow/break-continue-level.phpt b/tests/compiler/control_flow/break-continue-level.phpt index db92f7dd..72f91131 100644 --- a/tests/compiler/control_flow/break-continue-level.phpt +++ b/tests/compiler/control_flow/break-continue-level.phpt @@ -37,9 +37,13 @@ while ($i < 3) { } echo "break-2-while: done\n"; -// continue 2 from nested while +// continue 2 from nested while. The counter must advance before the +// inner loop: continue 2 jumps straight to the outer condition, so a +// trailing $i++ would never run and the loop would never terminate +// (PHP itself loops forever on that variant). $i = 0; while ($i < 3) { + $i++; $j = 0; while ($j < 3) { $j++; @@ -47,7 +51,6 @@ while ($i < 3) { continue 2; } } - $i++; } echo "continue-2-while: done\n"; From 22486ec56dc5336d2af4fac5c00dc2caeb41df7c Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:35:43 +0800 Subject: [PATCH 16/18] test: cover invalid multi-level loop control --- phpunit/src/NegativeCompatibilityTest.php | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/phpunit/src/NegativeCompatibilityTest.php b/phpunit/src/NegativeCompatibilityTest.php index 6a69a1b5..35be9337 100644 --- a/phpunit/src/NegativeCompatibilityTest.php +++ b/phpunit/src/NegativeCompatibilityTest.php @@ -145,6 +145,62 @@ function main(): void PHP, ]; + yield 'break level exceeds enclosing depth' => [ + 'convert', + "Cannot 'break' 2 levels", + <<<'PHP' + [ + 'convert', + "Cannot 'continue' 2 levels", + <<<'PHP' + [ + 'convert', + "'break' operator accepts only positive integer literals", + <<<'PHP' + [ + 'convert', + "'continue' operator accepts only positive integer literals", + <<<'PHP' + [ 'prepare', 'Cannot use Webman\\Route\\Route as Route because the name is already in use', From 45f6b8a16384455e5f6955d4b1e38dfb2dee7a18 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:50:02 +0800 Subject: [PATCH 17/18] chore(scripts): remove unused cleanup scripts --- clean-elf.sh | 34 -------- cleanup-typephp-tmp.sh | 178 ----------------------------------------- 2 files changed, 212 deletions(-) delete mode 100755 clean-elf.sh delete mode 100755 cleanup-typephp-tmp.sh diff --git a/clean-elf.sh b/clean-elf.sh deleted file mode 100755 index 4c47efb4..00000000 --- a/clean-elf.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# -# 删除根目录下编译临时产生的 ELF 可执行文件 -# - -DRY_RUN=false - -if [ "$1" = "--dry-run" ] || [ "$1" = "-n" ]; then - DRY_RUN=true - echo "==> DRY RUN MODE (不会实际删除) <==" -fi - -count=0 -deleted=0 - -while IFS=: read -r path type; do - case "$type" in - *ELF*executable*) - count=$((count + 1)) - if $DRY_RUN; then - echo " [DRY RUN] 将删除: $path" - else - rm -f "$path" && deleted=$((deleted + 1)) - echo " 已删除: $path" - fi - ;; - esac -done < <(find "$(dirname "$0")" -maxdepth 1 -type f -exec file {} \; 2>/dev/null) - -if $DRY_RUN; then - echo "==> 共发现 $count 个 ELF 可执行文件(未实际删除)。运行 ./clean-elf.sh 执行删除。" -else - echo "==> 共删除 $deleted 个 ELF 可执行文件。" -fi diff --git a/cleanup-typephp-tmp.sh b/cleanup-typephp-tmp.sh deleted file mode 100755 index f19e3020..00000000 --- a/cleanup-typephp-tmp.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -readonly MIN_AGE_MINUTES=60 - -dry_run=false -tmp_root=/tmp - -usage() { - cat <<'EOF' -Usage: ./cleanup-typephp-tmp.sh [options] - -Remove inactive TypePHP temporary files and directories from /tmp. -An entry is skipped when it or any of its descendants was modified or -metadata-changed during the last 60 minutes. -Recognized prefixes: typephp-, typephp_, utils_test_, and phpx-windows. - -Options: - -n, --dry-run Show what would be removed without deleting anything - --tmp-dir DIR Use another temporary directory (primarily for testing) - -h, --help Show this help -EOF -} - -while (($# > 0)); do - case "$1" in - -n | --dry-run) - dry_run=true - ;; - --tmp-dir) - if (($# < 2)); then - echo "Error: --tmp-dir requires a directory." >&2 - exit 2 - fi - tmp_root=$2 - shift - ;; - -h | --help) - usage - exit 0 - ;; - *) - echo "Error: unknown option: $1" >&2 - usage >&2 - exit 2 - ;; - esac - shift -done - -if [[ ! -d "$tmp_root" ]]; then - echo "Error: temporary directory does not exist: $tmp_root" >&2 - exit 1 -fi - -tmp_root=$(realpath -e -- "$tmp_root") -if [[ -z "$tmp_root" || "$tmp_root" == / ]]; then - echo "Error: refusing to use an unsafe temporary directory." >&2 - exit 1 -fi - -readonly tmp_root -readonly owner_uid=${SUDO_UID:-$(id -u)} - -format_size() { - local kib=$1 - awk -v kib="$kib" 'BEGIN { - if (kib >= 1048576) { - printf "%.2f GiB", kib / 1048576 - } else if (kib >= 1024) { - printf "%.2f MiB", kib / 1024 - } else { - printf "%d KiB", kib - } - }' -} - -entry_size_kib() { - local output - output=$(du -sk -- "$1" 2>/dev/null) || { - printf '0' - return - } - printf '%s' "${output%%$'\t'*}" -} - -has_recent_entry() { - local candidate=$1 - local recent - - # Check the complete tree. Looking only at the top-level directory mtime - # would miss writes to an existing file in a nested build directory. - if ! recent=$(find -P "$candidate" -xdev \ - \( -mmin "-${MIN_AGE_MINUTES}" -o -cmin "-${MIN_AGE_MINUTES}" \) \ - -printf '1' -quit 2>/dev/null); then - return 0 - fi - - [[ -n "$recent" ]] -} - -matched_count=0 -removed_count=0 -skipped_recent_count=0 -skipped_error_count=0 -total_kib=0 - -while IFS= read -r -d '' candidate; do - ((matched_count += 1)) - - # Keep the target constrained to one direct child of the selected root. - if [[ "$candidate" != "$tmp_root"/* || "${candidate%/*}" != "$tmp_root" ]]; then - echo "[skip unsafe] $candidate" >&2 - ((skipped_error_count += 1)) - continue - fi - - if has_recent_entry "$candidate"; then - echo "[skip recent] $candidate" - ((skipped_recent_count += 1)) - continue - fi - - size_kib=$(entry_size_kib "$candidate") - size=$(format_size "$size_kib") - - # The size scan can take noticeable time for a large build tree. Recheck - # freshness immediately before acting in case a compiler started using it. - if has_recent_entry "$candidate"; then - echo "[skip recent] $candidate" - ((skipped_recent_count += 1)) - continue - fi - - if $dry_run; then - echo "[would remove] $size $candidate" - ((removed_count += 1)) - ((total_kib += size_kib)) - continue - fi - - if [[ -d "$candidate" && ! -L "$candidate" ]]; then - if rm -rf --one-file-system -- "$candidate"; then - echo "[removed] $size $candidate" - ((removed_count += 1)) - ((total_kib += size_kib)) - else - echo "[skip error] failed to remove: $candidate" >&2 - ((skipped_error_count += 1)) - fi - elif rm -f -- "$candidate"; then - echo "[removed] $size $candidate" - ((removed_count += 1)) - ((total_kib += size_kib)) - else - echo "[skip error] failed to remove: $candidate" >&2 - ((skipped_error_count += 1)) - fi -done < <( - find -P "$tmp_root" -mindepth 1 -maxdepth 1 -uid "$owner_uid" \ - \( -name 'typephp-*' -o -name 'typephp_*' \ - -o -name 'utils_test_*' -o -name 'phpx-windows*' \) -print0 -) - -if $dry_run; then - action='would remove' -else - action='removed' -fi - -printf 'Summary: matched %d, %s %d (%s), skipped recent %d, errors %d.\n' \ - "$matched_count" "$action" "$removed_count" "$(format_size "$total_kib")" \ - "$skipped_recent_count" "$skipped_error_count" - -if ((skipped_error_count > 0)); then - exit 1 -fi From ee2afb08f739405da9be50b1eb2383785849c2a9 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 20:59:23 +0800 Subject: [PATCH 18/18] Translate all Chinese notes into English --- bin/dump-ast.php | 8 +- bin/extractor.php | 2 +- src/Backend/Clang.php | 6 +- src/Backend/CompilerBackend.php | 82 +++---- src/Backend/CompilerFactory.php | 26 +- src/Backend/Gcc.php | 2 +- src/Backend/GccLikeBackend.php | 22 +- src/Backend/Msvc.php | 52 ++-- src/Build/NativeBuildConfigurationTrait.php | 57 ++--- src/Build/SourcePipelineTrait.php | 33 +-- src/CompilerBase.php | 224 +++++++++++------- src/Context/CompilationStateTrait.php | 8 +- src/Entity/FunctionDef.php | 2 +- src/Extractor.php | 78 +++--- src/Generator/CallArgumentGenerator.php | 34 ++- src/Generator/ClosureGenerator.php | 3 +- src/Generator/ResourceFileGenerator.php | 77 +++--- src/Installer/LibPhpInstaller.php | 14 +- src/Metadata/Constants.php | 8 +- src/Optimizer/FuncCallOptimizer.php | 7 +- src/Parser/ArrayExpressionTrait.php | 9 +- src/Parser/AssignOpTrait.php | 12 +- src/Parser/BinaryOpTrait.php | 4 +- src/Parser/ForeachTrait.php | 6 +- src/Parser/FunctionCallTrait.php | 4 +- src/Parser/MethodCallTrait.php | 22 +- src/Parser/PropertyAccessTrait.php | 6 +- src/Parser/SwitchTrait.php | 2 +- src/Platform/PlatformBase.php | 60 ++--- src/Platform/UnixPlatform.php | 29 +-- src/Platform/Windows.php | 32 +-- src/Preprocessor.php | 40 ++-- src/PythonTools/Converter/PythonAstLoader.php | 4 +- .../Converter/PythonToTypePhpConverter.php | 26 +- src/Resolver/MagicMethodDetector.php | 2 +- src/Resolver/NameResolutionTrait.php | 26 +- src/Resolver/PropertyAccessResolver.php | 16 +- src/Resolver/Reflection.php | 4 +- src/Symbol/SymbolRepository.php | 5 +- src/Translator.php | 211 +++++++++-------- .../NativeTypeCompatibilityTrait.php | 53 +++-- src/compiler.php | 16 +- src/gen_stub.php | 18 +- 43 files changed, 736 insertions(+), 616 deletions(-) diff --git a/bin/dump-ast.php b/bin/dump-ast.php index 34524e1b..631aa6f0 100755 --- a/bin/dump-ast.php +++ b/bin/dump-ast.php @@ -2,11 +2,11 @@ + * Usage: php bin/dump-ast.php * - * 示例: + * Examples: * php bin/dump-ast.php examples/hello.php * php bin/dump-ast.php src/compiler.php */ @@ -231,7 +231,7 @@ function dumpNode(NodeAbstract $node, int $depth = 0): void $end = $node->getEndLine(); $subInfo = ''; - // 为常见节点类型提取关键信息 + // Extract key information for common node types. switch (true) { case $node instanceof Node\Expr\Variable: $subInfo = ' $' . ($node->name === null ? '(unset)' : (is_string($node->name) ? $node->name : '...')); diff --git a/bin/extractor.php b/bin/extractor.php index b479651f..683d6139 100644 --- a/bin/extractor.php +++ b/bin/extractor.php @@ -151,7 +151,7 @@ function extractorMain(array $argv): void } } -// 运行主函数 +// Run the main function. if (php_sapi_name() === 'cli') { extractorMain($argv); } diff --git a/src/Backend/Clang.php b/src/Backend/Clang.php index cbed8823..3e3b2d8e 100644 --- a/src/Backend/Clang.php +++ b/src/Backend/Clang.php @@ -7,7 +7,7 @@ use TypePhp\Platform\Windows; use TypePhp\Platform\Macos; /** - * Clang 编译器后端实现 + * Clang compiler backend implementation. */ class Clang extends GccLikeBackend { @@ -34,7 +34,7 @@ class Clang extends GccLikeBackend } /** - * Windows 下优先使用 lld-link,找不到时回退到 link.exe + * On Windows, prefer lld-link; fall back to link.exe if it is not available. */ public static function detectWindowsLinker(): string { @@ -62,7 +62,7 @@ class Clang extends GccLikeBackend return 'link'; } - // ──── 钩子方法覆盖 ──── + // ──── Hook method overrides ──── protected function getCompilerPrefixFlags(): string { diff --git a/src/Backend/CompilerBackend.php b/src/Backend/CompilerBackend.php index d20bab57..87b751ad 100644 --- a/src/Backend/CompilerBackend.php +++ b/src/Backend/CompilerBackend.php @@ -5,18 +5,18 @@ namespace TypePhp\Backend; use TypePhp\Platform\PlatformBase; /** - * 编译器后端抽象基类 - * 定义所有编译器必须实现的接口 + * Abstract base class for compiler backends. + * Defines the interface that all compilers must implement. */ abstract class CompilerBackend { /** - * 平台实例 + * The platform instance. */ protected PlatformBase $platform; /** - * 最近创建的 Response File 路径,用于构建完成后清理 + * Path of the most recently created Response File, used for cleanup after the build completes. */ protected string $lastResponseFile = ''; @@ -26,17 +26,17 @@ abstract class CompilerBackend } /** - * 获取编译器名称 + * Get the compiler name. */ abstract public function getName(): string; /** - * 获取编译器命令 + * Get the compiler command. */ abstract public function getCompilerCommand(): string; /** - * 获取链接器命令 + * Get the linker command. */ abstract public function getLinkerCommand(): string; @@ -51,7 +51,7 @@ abstract class CompilerBackend } /** - * 构建完整的编译命令 + * Build the complete compile command. */ abstract public function buildCompileCommand( string $sourceFile, @@ -60,7 +60,7 @@ abstract class CompilerBackend ): string; /** - * 构建 C 文件的编译命令(不包含 C++ 特定选项) + * Build the compile command for C files (excludes C++-specific options). */ abstract public function buildCCompileCommand( string $sourceFile, @@ -69,9 +69,9 @@ abstract class CompilerBackend ): string; /** - * 构建原生源文件的编译命令(汇编/Objective-C 等,使用 -x 指定语言) + * Build the compile command for native source files (assembly/Objective-C, etc., using -x to specify the language). * - * @param string $language GCC/Clang 语言标识(assembler, objective-c, objective-c++ 等) + * @param string $language GCC/Clang language identifier (assembler, objective-c, objective-c++, etc.) */ abstract public function buildNativeCompileCommand( string $sourceFile, @@ -81,7 +81,7 @@ abstract class CompilerBackend ): string; /** - * 构建完整的链接命令 + * Build the complete link command. */ abstract public function buildLinkCommand( array $objectFiles, @@ -90,33 +90,35 @@ abstract class CompilerBackend ): string; /** - * 构建编译选项(不含文件路径) - * @param array $config 编译配置 - * - optimize: 优化级别 (0-3) - * - debug_info: 是否生成调试信息 - * - sanitize: sanitizer 类型 (address, undefined, etc.) - * - cpp_std: C++ 标准版本 - * - is_zts: 是否为 ZTS 模式 - * - build_mode: 构建模式 ('bin' or 'ext') - * - enable_profiler: 是否启用性能分析 - * - suppressed_warnings: 需要屏蔽的警告代码数组 - * - cxxflags: 用户自定义编译标志 - * - compiler_pdb: MSVC 编译器 PDB 输出路径 + * Build compile options (excludes file paths). + * + * @param array $config Compile configuration + * - optimize: optimization level (0-3) + * - debug_info: whether to generate debug information + * - sanitize: sanitizer type (address, undefined, etc.) + * - cpp_std: C++ standard version + * - is_zts: whether ZTS mode is enabled + * - build_mode: build mode ('bin' or 'ext') + * - enable_profiler: whether to enable profiling + * - suppressed_warnings: array of warning codes to suppress + * - cxxflags: user-defined compile flags + * - compiler_pdb: MSVC compiler PDB output path */ abstract public function buildCompileOptions(array $config = []): string; /** - * 构建链接选项(不含文件路径) - * @param array $config 链接配置 - * - debug_info: 是否生成调试信息 - * - no_console: 是否隐藏控制台窗口 - * - build_mode: 构建模式 ('bin' or 'ext') - * - sanitize: sanitizer 类型 + * Build link options (excludes file paths). + * + * @param array $config Link configuration + * - debug_info: whether to generate debug information + * - no_console: whether to hide the console window + * - build_mode: build mode ('bin' or 'ext') + * - sanitize: sanitizer type */ abstract public function buildLinkOptions(array $config = []): string; /** - * 获取平台实例 + * Get the platform instance. */ public function getPlatform(): PlatformBase { @@ -124,7 +126,7 @@ abstract class CompilerBackend } /** - * 格式化包含路径 + * Format include paths. */ protected function formatIncludePaths(array $includePaths): string { @@ -132,7 +134,7 @@ abstract class CompilerBackend } /** - * 格式化库路径 + * Format library paths. */ protected function formatLibraryPaths(array $libraryPaths): string { @@ -140,7 +142,7 @@ abstract class CompilerBackend } /** - * 格式化库文件 + * Format library files. */ protected function formatLibraries(array $libraries): string { @@ -157,11 +159,11 @@ abstract class CompilerBackend } /** - * 将目标文件列表写入 Response File,避免命令行参数过长超出 OS 限制(Windows 8191 字符) + * Write the object file list to a Response File to avoid exceeding the OS command-line length limit (8191 characters on Windows). * - * @param array $objectFiles 目标文件路径列表 - * @param string $targetFile 最终输出文件路径(Response File 写入同目录) - * @return string 链接器参数,如 @build/project.rsp + * @param array $objectFiles List of object file paths. + * @param string $targetFile Final output file path (the Response File is written to the same directory). + * @return string Linker argument, e.g. @build/project.rsp */ protected function createResponseFile(array $objectFiles, string $targetFile): string { @@ -169,7 +171,7 @@ abstract class CompilerBackend $this->lastResponseFile = $rspFile; $lines = []; foreach ($objectFiles as $file) { - // 路径含空格时用双引号包裹,MSVC link.exe 和 GCC/Clang 均支持 + // Wrap paths containing spaces in double quotes; supported by both MSVC link.exe and GCC/Clang. if (str_contains($file, ' ')) { $file = '"' . $file . '"'; } @@ -180,7 +182,7 @@ abstract class CompilerBackend } /** - * 删除最近创建的 Response File 临时文件 + * Delete the most recently created Response File temporary file. */ public function cleanupResponseFile(): void { diff --git a/src/Backend/CompilerFactory.php b/src/Backend/CompilerFactory.php index 8fa86728..c2eee671 100644 --- a/src/Backend/CompilerFactory.php +++ b/src/Backend/CompilerFactory.php @@ -8,24 +8,24 @@ use TypePhp\Platform\Linux; use TypePhp\Platform\Macos; /** - * 编译器工厂类 - * 根据平台自动创建合适的编译器后端 + * Compiler factory. + * Automatically creates the appropriate compiler backend based on the platform. */ class CompilerFactory { /** - * 创建默认编译器后端 + * Create the default compiler backend. */ public static function create(PlatformBase $platform): CompilerBackend { if ($platform instanceof Windows) { - // Windows 默认使用 MSVC + // Windows uses MSVC by default. return new Msvc($platform, $platform->getDefaultCompiler()); } elseif ($platform instanceof Linux) { - // Linux 默认使用 GCC + // Linux uses GCC by default. return new Gcc($platform, $platform->getDefaultCompiler()); } elseif ($platform instanceof Macos) { - // macOS 默认使用 Clang + // macOS uses Clang by default. return new Clang($platform, $platform->getDefaultCompiler()); } else { throw new \RuntimeException("Unsupported platform: " . $platform->getName()); @@ -33,7 +33,7 @@ class CompilerFactory } /** - * 根据配置、环境变量和平台默认值解析编译器命令 + * Resolve the compiler command based on configuration, environment variables, and platform defaults. */ public static function detectCompilerName(PlatformBase $platform, string $configuredCompiler = ''): string { @@ -57,7 +57,7 @@ class CompilerFactory } /** - * 创建指定类型的编译器后端 + * Create a compiler backend of the specified type. */ public static function createByName(string $compilerName, PlatformBase $platform): CompilerBackend { @@ -93,17 +93,17 @@ class CompilerFactory } /** - * 自动检测并创建编译器和平台 + * Auto-detect and create the compiler and platform. */ public static function autoDetect(string $compilerName = '', ?PlatformBase $platform = null): array { - // 创建平台 + // Create the platform. $platform ??= \TypePhp\Platform\PlatformFactory::create(); - - // 创建编译器 + + // Create the compiler. $compilerName = self::detectCompilerName($platform, $compilerName); $compiler = self::createByName($compilerName, $platform); - + return [ 'platform' => $platform, 'compiler' => $compiler, diff --git a/src/Backend/Gcc.php b/src/Backend/Gcc.php index 1209b4d3..91fcb5cc 100644 --- a/src/Backend/Gcc.php +++ b/src/Backend/Gcc.php @@ -5,7 +5,7 @@ namespace TypePhp\Backend; use TypePhp\Platform\PlatformBase; /** - * GCC 编译器后端实现 + * GCC compiler backend implementation. */ class Gcc extends GccLikeBackend { diff --git a/src/Backend/GccLikeBackend.php b/src/Backend/GccLikeBackend.php index 8179ade5..b83e8274 100644 --- a/src/Backend/GccLikeBackend.php +++ b/src/Backend/GccLikeBackend.php @@ -6,9 +6,9 @@ use TypePhp\Platform\PlatformBase; use TypePhp\Platform\Windows; /** - * GCC/Clang 共享后端基类 - * 包含 Unix-like 编译器(GCC、Clang)的通用命令行构建逻辑。 - * 子类只需覆盖平台差异的钩子方法。 + * Shared backend base class for GCC/Clang. + * Contains the common command-line construction logic for Unix-like compilers (GCC, Clang). + * Subclasses only need to override the platform-specific hook methods. */ abstract class GccLikeBackend extends CompilerBackend { @@ -37,21 +37,21 @@ abstract class GccLikeBackend extends CompilerBackend return $headerFile . '.gch'; } - // ──── 钩子方法(子类覆盖点) ──── + // ──── Hook methods (subclass override points) ──── - /** 编译器特定的前缀标志(如 MSVC 兼容模式) */ + /** Compiler-specific prefix flags (e.g. MSVC compatibility mode). */ protected function getCompilerPrefixFlags(): string { return ''; } - /** 链接器输出标志(-o vs /OUT:) */ + /** Linker output flag (-o vs /OUT:). */ protected function getLinkerOutputFlag(): string { return '-o'; } - /** 格式化 sanitizer 标志 */ + /** Format the sanitizer flag. */ protected function formatSanitizerFlag(string $sanitizer): string { return match ($sanitizer) { @@ -61,7 +61,7 @@ abstract class GccLikeBackend extends CompilerBackend }; } - /** 获取 PIC 标志 */ + /** Get the PIC flag. */ protected function getPICFlag(array $config): string { if ((!empty($config['build_mode']) && ($config['build_mode'] === 'ext' || $config['build_mode'] === 'lib')) || !empty($config['pic'])) { @@ -70,7 +70,7 @@ abstract class GccLikeBackend extends CompilerBackend return ''; } - /** 构建 GCC/Clang 共享编译选项,C 和 C++ 编译路径都复用这里 */ + /** Build the shared GCC/Clang compile flags; reused by both the C and C++ compilation paths. */ protected function buildSharedCompileFlags(array $config, bool $includeCppStd = false): string { $cmd = ''; @@ -146,7 +146,7 @@ abstract class GccLikeBackend extends CompilerBackend return ' -include ' . escapeshellarg($precompiledHeader['header']); } - /** 获取平台特定的链接选项 */ + /** Get platform-specific link options. */ protected function getPlatformLinkFlags(array $config): string { $flags = ''; @@ -181,7 +181,7 @@ abstract class GccLikeBackend extends CompilerBackend return $flags; } - // ──── 抽象方法实现 ──── + // ──── Abstract method implementations ──── public function buildCompileCommand(string $sourceFile, string $outputFile, array $options = []): string { diff --git a/src/Backend/Msvc.php b/src/Backend/Msvc.php index 64883133..3e70af5d 100644 --- a/src/Backend/Msvc.php +++ b/src/Backend/Msvc.php @@ -5,7 +5,7 @@ namespace TypePhp\Backend; use TypePhp\Platform\Windows; /** - * MSVC 编译器后端实现 + * MSVC compiler backend implementation. */ class Msvc extends CompilerBackend { @@ -131,7 +131,7 @@ class Msvc extends CompilerBackend } /** - * 构建 C 文件的编译命令(不包含 C++ 特定选项) + * Build the compile command for C files (excludes C++-specific options). */ public function buildCCompileCommand(string $sourceFile, string $outputFile, array $options = []): string { @@ -145,20 +145,20 @@ class Msvc extends CompilerBackend $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); } - // 平台宏定义 + // Platform macro definitions. $cmd .= $this->buildCommonCompileFlags($options, false); - // 注意:C 文件不使用 /EHsc, /std:c++17, /MD 等 C++ 特定选项 + // Note: C files do not use C++-specific options such as /EHsc, /std:c++17, /MD. return $cmd; } /** - * 构建原生源文件的编译命令 + * Build the compile command for native source files. * - * MSVC 仅支持 C 文件(/TC),汇编和 ObjC 文件不受支持 + * MSVC only supports C files (/TC); assembly and ObjC files are not supported. * - * @param string $language 语言标识 + * @param string $language Language identifier. */ public function buildNativeCompileCommand(string $sourceFile, string $outputFile, array $options = [], string $language = ''): string { @@ -214,42 +214,42 @@ class Msvc extends CompilerBackend } /** - * 构建编译选项(实现抽象方法) + * Build compile options (implements the abstract method). */ public function buildCompileOptions(array $config = []): string { return $this->buildCommonCompileFlags($config, true); } - + /** - * 构建链接选项(实现抽象方法) + * Build link options (implements the abstract method). */ public function buildLinkOptions(array $config = []): string { $cmd = ''; - - // 调试 + + // Debug. if (!empty($config['debug'])) { $cmd .= ' /DEBUG'; } - // Windows 子系统 + // Windows subsystem. if (!empty($config['no_console'])) { $cmd .= ' ' . $this->platform->getSubsystemOptions(true); } - // CRT 配置 + // CRT configuration. $cmd .= ' ' . $this->platform->getCrtConfig(); - // 扩展模块选项 + // Extension module options. if (!empty($config['build_mode']) && ($config['build_mode'] === 'ext' || $config['build_mode'] === 'lib')) { $cmd .= ' /DLL'; } - // nologo + // nologo. $cmd .= ' /nologo'; - // LTO(链接时代码生成) + // LTO (Link Time Code Generation). if (!empty($config['lto'])) { $cmd .= ' /LTCG'; } @@ -258,20 +258,20 @@ class Msvc extends CompilerBackend } /** - * 编译 Windows 资源文件 (.rc) 为目标文件 (.res) + * Compile a Windows resource file (.rc) into an object file (.res). * - * 使用 rc.exe(MSVC 资源编译器)将 .rc 文件编译为 .res 文件 - * .res 文件可以直接传给 link.exe 作为输入 + * Uses rc.exe (the MSVC resource compiler) to compile a .rc file into a .res file. + * The .res file can be passed directly to link.exe as input. * - * @param string $rcFile 资源文件路径 (.rc) - * @param string $resFile 输出资源文件路径 (.res) - * @return string 编译命令 + * @param string $rcFile Resource file path (.rc). + * @param string $resFile Output resource file path (.res). + * @return string The compile command. */ public function compileResourceFile(string $rcFile, string $resFile): string { - // rc.exe 是 MSVC 自带的资源编译器 - // /nologo: 不显示版权信息 - // /fo: 指定输出文件 + // rc.exe is the resource compiler bundled with MSVC. + // /nologo: suppress the copyright banner. + // /fo: specify the output file. $cmd = 'rc.exe /nologo'; $cmd .= ' /fo ' . escapeshellarg($resFile); $cmd .= ' ' . escapeshellarg($rcFile); diff --git a/src/Build/NativeBuildConfigurationTrait.php b/src/Build/NativeBuildConfigurationTrait.php index bc9dd32b..dc28d40c 100644 --- a/src/Build/NativeBuildConfigurationTrait.php +++ b/src/Build/NativeBuildConfigurationTrait.php @@ -20,7 +20,7 @@ trait NativeBuildConfigurationTrait $this->getPhpxDir() . '/src/misc', ]; - // 根据平台添加 PHP 包含路径 + // Add the platform-specific PHP include paths if ($platform instanceof Windows) { $phpSdkPaths = $platform->buildPhpSdkIncludePaths($this->getPhpDir()); $includePaths = array_merge($includePaths, $phpSdkPaths); @@ -28,7 +28,7 @@ trait NativeBuildConfigurationTrait // Linux/macOS $phpPaths = $platform->buildPhpIncludePaths($this->getPhpDir()); $includePaths = array_merge($includePaths, $phpPaths); - // 内置 mpdecimal 头文件目录 + // Bundled mpdecimal header directories $includePaths[] = $this->getPhpxDir() . '/thirdparty/mpdecimal/libmpdec'; $includePaths[] = $this->getPhpxDir() . '/thirdparty/mpdecimal/libmpdec++'; } @@ -43,7 +43,7 @@ trait NativeBuildConfigurationTrait $this->getPhpxDir() . '/lib', ]; - // 根据平台添加 PHP 库路径 + // Add the platform-specific PHP library paths if ($platform instanceof Windows) { $phpLibPaths = $platform->buildPhpSdkLibPaths($this->getPhpDir()); $libraryPaths = array_merge($libraryPaths, $phpLibPaths); @@ -57,45 +57,45 @@ trait NativeBuildConfigurationTrait } /** - * 获取库文件 + * Get the library files to link against */ protected function getLibraries(): array { $platform = $this->getPlatform(); $libraries = []; - // phpx 库(根据平台使用不同的文件名格式) + // phpx library (file name format differs by platform) $phpxLibPath = $this->findPhpxLibrary(); if ($phpxLibPath === null) { $this->error($this->getPhpxLibraryErrorMessage()); } $libraries[] = $phpxLibPath; - // extension 和 bin 模式都需要链接 PHP 库 + // Both extension and bin modes need to link the PHP library if ($platform instanceof Windows) { - // Windows: 根据构建模式选择不同的库 + // Windows: pick different libraries based on the build mode if ($this->isBuildModeEmbed()) { - // bin 模式:需要同时链接 php8ts.lib 和 php8embed.lib - // 注意:php8ts.lib 必须在 php8embed.lib 之前,因为 embed 依赖 core - // php8ts.lib 提供 PHP 核心全局符号(executor_globals, compiler_globals, sapi_globals) + // bin mode: link both php8ts.lib and php8embed.lib + // Note: php8ts.lib must come before php8embed.lib because embed depends on core + // php8ts.lib provides the PHP core global symbols (executor_globals, compiler_globals, sapi_globals) if (!empty($this->windowsPhpCoreLib)) { - $libraries[] = $this->windowsPhpCoreLib; // 不添加引号 + $libraries[] = $this->windowsPhpCoreLib; // do not quote } - // php8embed.lib 提供嵌入 API + // php8embed.lib provides the embed API if (!empty($this->windowsPhpEmbedLib)) { - $libraries[] = $this->windowsPhpEmbedLib; // 不添加引号 + $libraries[] = $this->windowsPhpEmbedLib; // do not quote } } else { - // ext 模式:只使用 php8ts.lib 或 php8.lib(PHP 扩展) + // ext mode: use only php8ts.lib or php8.lib (PHP extension) if (!empty($this->windowsPhpCoreLib)) { - $libraries[] = $this->windowsPhpCoreLib; // 不添加引号 + $libraries[] = $this->windowsPhpCoreLib; // do not quote } } - // 添加 Windows API 库(Win32 GUI 程序需要) - $libraries[] = 'user32.lib'; // Windows UI 函数(CreateWindow, MessageBox 等) - $libraries[] = 'gdi32.lib'; // GDI 图形函数 - $libraries[] = 'kernel32.lib'; // 核心 Windows API + // Add the Windows API libraries (required by Win32 GUI programs) + $libraries[] = 'user32.lib'; // Windows UI functions (CreateWindow, MessageBox, etc.) + $libraries[] = 'gdi32.lib'; // GDI graphics functions + $libraries[] = 'kernel32.lib'; // Core Windows API $libraries[] = 'gmp.lib'; $libraries[] = 'gmpxx.lib'; $libraries[] = 'mpfr.lib'; @@ -117,10 +117,12 @@ trait NativeBuildConfigurationTrait } /** - * 解析 phpx 库文件路径,库不存在时返回 null。 + * Resolve the phpx library file path, returning null when the library does + * not exist. * - * Windows 使用 phpx.lib(无 lib 前缀);其他平台优先使用共享库 - * (libphpx.so / libphpx.dylib),找不到时回退到静态库 libphpx.a。 + * Windows uses phpx.lib (no lib prefix); other platforms prefer the shared + * library (libphpx.so / libphpx.dylib) and fall back to the static library + * libphpx.a when it is not found. */ protected function findPhpxLibrary(): ?string { @@ -131,8 +133,8 @@ trait NativeBuildConfigurationTrait return is_file($phpxLibPath) ? $phpxLibPath : null; } - // Linux/macOS:共享库优先,静态库兜底 - // getSharedLibraryExtension() 返回的值可能带点或不带点,需要统一处理 + // Linux/macOS: prefer the shared library, fall back to the static library + // getSharedLibraryExtension() may or may not include a leading dot, so normalize it $sharedLibExt = ltrim($platform->getSharedLibraryExtension(), '.'); $phpxLibPath = $this->getPhpxDir() . '/lib/libphpx.' . $sharedLibExt; if (is_file($phpxLibPath)) { @@ -152,7 +154,7 @@ trait NativeBuildConfigurationTrait } /** - * 生成 phpx 库缺失时的错误信息 + * Generate the error message shown when the phpx library is missing */ protected function getPhpxLibraryErrorMessage(): string { @@ -175,8 +177,9 @@ trait NativeBuildConfigurationTrait } /** - * 前置检测 phpx 库是否可用,在编译开始前报错, - * 避免所有源文件编译完成后才在链接阶段失败。 + * Verify the phpx library is available up front and fail before compilation + * starts, rather than only failing at link time after all source files have + * been compiled. */ protected function validatePhpxLibrary(): void { diff --git a/src/Build/SourcePipelineTrait.php b/src/Build/SourcePipelineTrait.php index 4fbf35e8..4514e8a8 100644 --- a/src/Build/SourcePipelineTrait.php +++ b/src/Build/SourcePipelineTrait.php @@ -34,7 +34,7 @@ trait SourcePipelineTrait $path = $realpath; if (is_dir($path)) { - // 目录模式:不解析 YAML + // Directory mode: no YAML parsing $list = $this->getFilesFromDir($path); $targetName = basename($path); $this->setTargetName($targetName); @@ -42,10 +42,10 @@ trait SourcePipelineTrait } else { $ext = pathinfo($path, PATHINFO_EXTENSION); if ($ext === 'yml' || $ext === 'yaml') { - // YAML 配置模式:先解析 YAML + // YAML config mode: parse the YAML first $list = $this->parseProjectYaml($path); } elseif ($ext === 'php') { - // 单文件模式:不解析 YAML + // Single-file mode: no YAML parsing $list = [$path]; $targetName = FileScanner::getFileName($path); $this->setTargetName($targetName); @@ -55,7 +55,8 @@ trait SourcePipelineTrait } } - // 在所有配置加载完成后,应用命令行参数(确保优先级最高) + // Apply command-line arguments after all configuration is loaded (so they + // take the highest precedence) $this->applyCommandLineArguments(); // The generated public import stub is an output artifact, not an input @@ -100,19 +101,22 @@ trait SourcePipelineTrait } } - // 仅在 PHP 脚本入口(bin/tpc.php)前置检测 phpx 库:缺少库立即 fatal, - // 避免继续向下执行到文件处理/编译阶段才报错。已编译的 tpc 可执行文件 - // 在进入 main() 前就由动态链接器加载 libphpx,无需(也无法)在此检测。 + // Pre-check the phpx library only at the PHP script entry (bin/tpc.php): + // a missing library fails immediately rather than surfacing later during + // file processing/compilation. The compiled tpc executable has libphpx + // loaded by the dynamic linker before entering main(), so checking here + // is neither needed nor possible. if (defined('TYPEPHP_PHP_SCRIPT_ENTRY') && !($this->getPlatform() instanceof Wasi)) { $this->validatePhpxLibrary(); } $this->validateCompilerToolchain(); - // shell_exec 和 define 已通过 php::fn:: 直接调用,无需动态符号表 + // shell_exec and define are already called directly via php::fn::, so no + // dynamic symbol table is needed - // Windows 的所有构建模式都依赖 PHPX 导入库和运行库。 - // 其他平台仅在嵌入式构建模式下执行现有检查。 + // All Windows build modes depend on the PHPX import library and runtime. + // Other platforms only run the existing checks in embedded build mode. if ($this->isBuildModeEmbed() || $this->getPlatform() instanceof Windows) { foreach ($this->getPlatform()->getBuildLibraryWarnings( $this->getPhpDir(), @@ -136,7 +140,7 @@ trait SourcePipelineTrait $files = $this->filterIgnoredFiles($files); $this->discoverNativeClassDeclarations($files); - // 分析 PHP 文件,预处理 + // Analyze and preprocess the PHP files foreach ($files as $k => $file) { if (FileScanner::isPhpFile($file)) { try { @@ -258,7 +262,7 @@ trait SourcePipelineTrait $sourceFiles = []; $validSourceCount = 0; - // 生成 C++ 文件 + // Generate the C++ files foreach ($files as $k => $file) { try { if (FileScanner::isPhpFile($file)) { @@ -293,10 +297,11 @@ trait SourcePipelineTrait $this->genLibraryImportStub($files); } - // 生成构建期内部头文件:函数声明、运行时数据声明 + // Generate the build-time internal headers: function declarations and + // runtime data declarations $this->genFunctionDeclarations($this->getIncludeDir() . "/php_{$this->targetName}_func_decl.h"); $this->genDataDeclarations($this->getIncludeDir() . "/php_{$this->targetName}_data_decl.h"); - // 生成扩展模块源文件 + // Generate the extension module source file $sourceFiles[] = $this->genExtension(); return $sourceFiles; diff --git a/src/CompilerBase.php b/src/CompilerBase.php index dce4d484..c5327c65 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -288,12 +288,14 @@ class CompilerBase implements PropertyAccessContext protected int $classIndex = 0; /** - * 用户定义(请求生命周期)类名 → ID,运行期为 THREAD_LOCAL 缓存,RSHUTDOWN 清理 + * User-defined (request-lifetime) class name → ID. Backed by a THREAD_LOCAL + * cache at runtime and cleared on RSHUTDOWN. * @var array */ protected array $classMap = []; /** - * 内置/编译产物(模块生命周期)类名 → ID,PHP 启动完成后惰性填充,RSHUTDOWN 不清理 + * Built-in / compiled-output (module-lifetime) class name → ID. Lazily + * populated after PHP startup and NOT cleared on RSHUTDOWN. * @var array */ protected array $persistentClassMap = []; @@ -305,21 +307,26 @@ class CompilerBase implements PropertyAccessContext protected int $funcIndex = 0; /** - * 用户定义(请求生命周期)函数/方法 → ID,运行期为 THREAD_LOCAL 缓存,RSHUTDOWN 清理 - * key 为函数名或 `Class::method` + * User-defined (request-lifetime) function/method → ID. Backed by a + * THREAD_LOCAL cache at runtime and cleared on RSHUTDOWN. + * Key is a function name or `Class::method`. * @var array */ protected array $funcMap = []; /** - * 内置/编译产物(模块生命周期)函数/方法 → ID,PHP 启动完成后惰性填充,RSHUTDOWN 不清理 - * key 为函数名或 `Class::method` + * Built-in / compiled-output (module-lifetime) function/method → ID. Lazily + * populated after PHP startup and NOT cleared on RSHUTDOWN. + * Key is a function name or `Class::method`. * @var array */ protected array $persistentFuncMap = []; protected int $persistentFuncIndex = 0; /** - * 内置/编译产物类的声明属性 offset 缓存,key 为 `Class::prop`,惰性填充,RSHUTDOWN 不清理。 - * 属性解析仅覆盖编译类与内置类的声明属性(进程级稳定),用户类属性走字符串路径,不进缓存。 + * Declared-property offset cache for built-in / compiled-output classes. + * Key is `Class::prop`; lazily populated and NOT cleared on RSHUTDOWN. + * Property resolution only covers declared properties of compiled classes + * and built-in classes (process-stable). User-class properties go through + * the string path and never enter this cache. */ protected array $persistentPropMap = []; protected int $persistentPropIndex = 0; @@ -345,10 +352,11 @@ class CompilerBase implements PropertyAccessContext 'mixed' => Type::VAR, 'null' => Type::VAR, 'any' => Type::VAR, - // callable 类型,可以是字符串、数组、对象 - // 1) 'foo' 函数名称字符串, 2) [ $obj, 'bar' ] 对象方法数组, 3) Closure 对象, 4) [ 'class', 'staticMethod'] 类名+静态方法数组 + // The callable type can be a string, array, or object: + // 1) 'foo' function-name string, 2) [ $obj, 'bar' ] object-method array, + // 3) a Closure object, 4) [ 'class', 'staticMethod' ] class + static-method array. 'callable' => Type::VAR, - // iterable 类型,可以是数组或者对象 + // The iterable type can be an array or an object. 'iterable' => Type::VAR, 'stream' => Type::STREAM, 'bigint' => Type::BIGINT, @@ -361,13 +369,15 @@ class CompilerBase implements PropertyAccessContext protected array $internalConstants = []; /** - * 存储所有函数、类方法的声明,key 是 符号名称,Value 是函数、类方法所在的文件名称 + * Stores the declaration of every function and class method. Key is the + * symbol name; value is the file in which the function or method is declared. * @var array */ protected array $symbolDeclInFile = []; /** - * 存储所有函数、类方法的调用,key 是 文件名称,Value 是函数、类方法调用的列表数组 + * Stores every function / class-method call. Key is the file name; value is + * a list of the functions / class methods called within that file. * @var array> */ protected array $symbolCallInFile = []; @@ -400,7 +410,7 @@ class CompilerBase implements PropertyAccessContext protected string $dir; /** - * 原始值,可能包含 `\\` 多层空间. + * The raw namespace value, which may contain `\\` multi-level separators. */ protected string $namespace = ''; protected string $method = ''; @@ -413,7 +423,7 @@ class CompilerBase implements PropertyAccessContext protected array $useImportAliases = []; /** - * 原始类名,不包含命名空间. + * The raw class name, without the namespace. */ protected string $class = ''; protected string $parentClass = ''; @@ -469,7 +479,7 @@ class CompilerBase implements PropertyAccessContext protected bool $bigintTypes = false; protected string $rootPath; protected string $buildDir; - protected string $outputDir = ''; // -o 参数指定的输出目录 + protected string $outputDir = ''; // Output directory specified by the -o option protected int $debugLine = 0; protected CLImate $climate; protected bool $stubFile = false; @@ -483,26 +493,28 @@ class CompilerBase implements PropertyAccessContext protected Parser $parser; protected string $phpVersion = self::DEFAULT_PHP_VERSION; protected PrettyPrinter $printer; - protected bool $isPhpZts = false; // PHP 是否为线程安全版本 + protected bool $isPhpZts = false; // Whether the PHP build is thread-safe (ZTS) - // Windows 平台:保存检测到的 PHP lib 文件路径 - protected string $windowsPhpEmbedLib = ''; // php8embed.lib 路径 - protected string $windowsPhpCoreLib = ''; // php8ts.lib 或 php8.lib 路径 + // Windows platform: store the detected PHP lib file paths. + protected string $windowsPhpEmbedLib = ''; // Path to php8embed.lib + protected string $windowsPhpCoreLib = ''; // Path to php8ts.lib or php8.lib - // 新的平台和编译器抽象层(可选使用) + // New platform and compiler abstraction layers (optional to use). protected ?PlatformBase $platform = null; protected ?CompilerBackend $compilerBackend = null; /** - * 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题 - * `static::methodCall()` - * `$this->methodCall()` 子类和父类中存在同名方法 + * Records all class method names collected during the preprocessing phase. + * Used to detect methods with the same name declared in both a child class + * and its parent class, resolving dynamic method-binding calls such as + * `static::methodCall()` and `$this->methodCall()` where a parent and a + * child class both define the method. * @var array */ protected array $classMethodOverride = []; /** - * 存储所有类继承关系,类名必须全部为小写 + * Stores all class inheritance relationships. Class names must be all lowercase. * @var array */ protected SymbolRepository $symbols; @@ -1183,8 +1195,10 @@ class CompilerBase implements PropertyAccessContext } /** - * 判断类的符号指针是否在 PHP 模块生命周期内稳定(MINIT 注册,跨请求缓存安全)。 - * 编译产物(本单元编译的类/接口)与 PHP 内置类/接口均满足条件。 + * Determine whether a class's symbol pointer is stable across the PHP + * module lifetime (registered at MINIT, safe to cache across requests). + * Compiled output (classes/interfaces compiled in this unit) and PHP + * built-in classes/interfaces both satisfy this condition. */ protected function isProcessStableClass(string $className): bool { @@ -1196,8 +1210,9 @@ class CompilerBase implements PropertyAccessContext } /** - * 判断函数/方法符号指针是否在 PHP 模块生命周期内稳定。 - * `Class::method` 形式的 key 以其所属类的稳定性为准。 + * Determine whether a function/method symbol pointer is stable across the + * PHP module lifetime. For a `Class::method` key, stability is determined + * by the class it belongs to. */ protected function isProcessStableFunction(string $funcName): bool { @@ -1251,13 +1266,15 @@ class CompilerBase implements PropertyAccessContext } /** - * @param string $className 必须是带有命名空间的完整类名 + * @param string $className Must be a fully-qualified class name (with namespace). * - * 注意:不存在与用户定义类对应的动态 propMap(区别于 classMap/funcMap)。 - * 属性 offset 缓存的前提是编译期能解析出声明属性(PropertyAccessResolver - * 只接受编译类的 ClassDef 或内置类的反射声明属性),用户类在编译期不可见, - * 其属性访问一律走 `.attr(name)` 字符串路径,因此所有条目必然进程级稳定, - * 全部进入 persistentPropMap。 + * Note: there is no dynamic propMap for user-defined classes (unlike + * classMap/funcMap). The property-offset cache assumes declared properties + * can be resolved at compile time (PropertyAccessResolver only accepts a + * compiled ClassDef or the reflected declared properties of a built-in + * class). User classes are not visible at compile time, so their property + * accesses always go through the `.attr(name)` string path. As a result, + * every entry is necessarily process-stable and goes into persistentPropMap. */ protected function getPropertyId(string $className, string $propName): int { @@ -1555,10 +1572,12 @@ class CompilerBase implements PropertyAccessContext } return $this->withoutLocalClassEntryHoisting(function () use ($default): string { /* - * 函数参数默认值只能为字面量,无法使用表达式获取值。 - * 但 PHP 自 5.6 起支持在默认参数值中使用常量表达式,包括 - * 类常量(self::FOO、ClassName::BAR、\Full\Class::BAZ), - * 编译器需要在编译期将其折叠为对应的字面量。 + * Function parameter default values may only be literals; they + * cannot be obtained through an expression. Since PHP 5.6, however, + * constant expressions are allowed in default parameter values, + * including class constants (self::FOO, ClassName::BAR, + * \Full\Class::BAZ). The compiler must fold these into the + * corresponding literal at compile time. * * PHP 8.1 also permits `new` in selected default-value contexts. * These expressions are emitted into standalone helper functions, @@ -1585,8 +1604,9 @@ class CompilerBase implements PropertyAccessContext } /** - * 在 for/foreach 等包含子语句的语句,之前检查当前待添加的代码是否为空, - * 如果不为空,需要将语句追加到 {} 作用域符号之前. + * For statements containing sub-statements (for/foreach, etc.), check + * whether the currently pending code is empty. If not, the pending + * statements must be emitted before the opening `{` scope brace. */ protected function parseBeforeStmtLines(): string { @@ -1914,7 +1934,7 @@ class CompilerBase implements PropertyAccessContext } /** - * 尽可能转为数字,优先级 浮点 > 整数 > 字符串. + * Convert to a number whenever possible, with priority float > integer > string. */ protected function parseNumericIdentifier(NodeAbstract $expr): string { @@ -2002,7 +2022,7 @@ class CompilerBase implements PropertyAccessContext if ($this->classDef?->nativeObject) { $this->fatalError($expr, 'Native classes do not support `new static()`'); } - // 无法在编译期获得 static 类的准确类名 + // The exact class name of a `static` class cannot be obtained at compile time. return ''; } else { return $this->getNamespacedClassName($class); @@ -2104,10 +2124,10 @@ class CompilerBase implements PropertyAccessContext protected function detectDeclaredClassOfExpr(NodeAbstract $expr): string { - // 对象表达式有两类类型信息: - // 1. detectClassOfExpr() 返回“实际可推断的类”,例如 new Foo()、typed object 变量; - // 2. getDeclaredObjectType() 返回变量声明/首次赋值记录的 declared type,可能是接口或抽象类。 - // 参数和属性赋值检查需要先使用实际类;实际类不可知时才退回 declared type。 + // Object expressions carry two kinds of type information: + // 1. detectClassOfExpr() returns the "actually inferable class", e.g. new Foo() or a typed object variable; + // 2. getDeclaredObjectType() returns the declared type recorded at declaration/first assignment, which may be an interface or abstract class. + // Parameter and property-assignment checks prefer the actual class, falling back to the declared type only when the actual class is unknown. $class = $this->detectClassOfExpr($expr); if ($class !== '') { return $class; @@ -2120,13 +2140,22 @@ class CompilerBase implements PropertyAccessContext protected function isObjectClassStaticallyAssignableTo(string $class, string $expected): bool { - // 这个函数只回答“编译器在静态阶段能否证明 $class is-a $expected”。 - // 这里禁止使用 class_exists()/interface_exists()/is_a() 去查询当前运行编译器的 PHP 进程: - // - 编译器进程已加载的 Composer/工具类,不等价于被编译项目运行时可用的类; - // - 自举编译时还会把编译器自身依赖的外部库误判为项目静态类; - // - AOT 的静态判断必须只依赖 hasClass()/hasInterface() 记录的项目类图,或明确的内置类/接口。 - // 如果类不属于这些集合,说明它是动态类/外部库类,不能在这里静态判定,应返回 false, - // 由调用处决定是延迟到运行时 php::toObject()/TypeCheck,还是因为确定 concrete mismatch 而 fatal。 + // This function only answers "can the compiler prove at the static + // stage that $class is-a $expected". It must not use + // class_exists()/interface_exists()/is_a() to query the PHP process + // currently running the compiler: + // - Composer/tool classes already loaded in the compiler process are not + // equivalent to classes available at runtime for the compiled project; + // - during bootstrapping, the compiler's own external dependencies would + // be mistaken for the project's static classes; + // - AOT static analysis must rely only on the project class graph + // recorded by hasClass()/hasInterface(), or on explicitly built-in + // classes/interfaces. + // If a class is not in one of these sets, it is a dynamic / external + // library class and cannot be statically determined here. Return false + // and let the caller decide whether to defer to runtime + // php::toObject()/TypeCheck, or to fail fatally because of a + // determined concrete mismatch. $class = ltrim($class, '\\'); $expected = ltrim($expected, '\\'); if (strcasecmp($class, $expected) === 0) { @@ -2146,10 +2175,13 @@ class CompilerBase implements PropertyAccessContext protected function isKnownConcreteObjectExpr(NodeAbstract $expr, string $class): bool { - // “已知 concrete object” 的要求比“表达式写着 new SomeClass”更严格: - // 只有 AOT 项目类图中的类或内置类,编译器才能在静态阶段确认其继承关系。 - // 外部库类即使出现在 new 表达式中,也不能用当前编译器进程的反射信息判定, - // 否则会把编译器/Composer 运行环境泄漏进被编译项目的类型系统。 + // "Known concrete object" is stricter than "the expression literally + // says new SomeClass": only classes in the AOT project class graph or + // built-in classes allow the compiler to confirm inheritance at the + // static stage. Even if an external library class appears in a new + // expression, it cannot be determined using the reflection info of the + // current compiler process; doing so would leak the compiler/Composer + // runtime environment into the type system of the compiled project. if ($class === '' || $this->isInterface($class) || $this->isAbstractClass($class)) { return false; } @@ -2390,7 +2422,7 @@ class CompilerBase implements PropertyAccessContext $lines[] = 'return ' . $tuple . ';'; return implode(PHP_EOL . $this->getIndent(), $lines); } - // 实际函数的返回值 + // The return value of the actual function. $type = $this->detectTypeOfExpr($v->expr); // In ordinary PHP mode, int +/−/* int is only conditionally an int: // runtime overflow promotes the result to float. Keep the Variant @@ -2488,7 +2520,7 @@ class CompilerBase implements PropertyAccessContext $expr = $this->parseExprAsValue($v->expr); $returnType = $this->getReturnType(); - // 匿名函数的返回值一定是 var + // The return value of an anonymous function is always var. if (!$this->context->inClosure) { if ($returnType === Type::VOID) { $this->fatalError($v, 'The return type is void, cannot return any value'); @@ -2511,7 +2543,7 @@ class CompilerBase implements PropertyAccessContext } $returnObjectCheckClass = ''; - // 返回值的表达式是一个类的对象 + // The return-value expression is an instance of a class. $objectClass = $this->detectDeclaredClassOfExpr($v->expr); $returnClass = $this->context->inClosure ? '' : $this->getReturnClass(); if ($returnClass) { @@ -2537,13 +2569,17 @@ class CompilerBase implements PropertyAccessContext [$code, $tmpVar] = $this->genUnionCheckedReturnAssignment($exprCode); $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';'; } elseif (!$this->isVarExpr($v->expr) and !$this->isScalar($v->expr)) { - // return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针 - // 将 Indirect 赋值给临时变量后,使用 Ctor::Copy 解除了 Indirect,保证内存安全 + // If return uses an Indirect statement, the variable may be + // destructed early, producing a dangling pointer. Assign the + // Indirect to a temporary variable; Ctor::Copy releases the + // Indirect, guaranteeing memory safety. $tmpVar = $this->genTmpVarName(); - // 必须提前声明变量,否则在末尾声明并 return 可能会被 gcc 优化掉 + // The variable must be declared up front; otherwise declaring it at + // the end and returning it could be optimized away by gcc. $this->addLocalVar($tmpVar, $returnType); $code = $tmpVar . ' = (' . $exprCode . ');' . PHP_EOL; - // 解析表达式后可能会插入语句,因此需要在末尾添加 return 语句,而不是直接返回 + // Parsing the expression may insert statements, so the return + // statement must be appended at the end rather than returned directly. $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';'; } else { $code = 'return ' . $exprCode . ';'; @@ -2715,7 +2751,8 @@ class CompilerBase implements PropertyAccessContext $classDef = $this->getClass($class); $methodDef = null; - // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 + // Search recursively: if the method is not defined in the child class, + // try to find it in the parent class. while (true) { if (!$classDef->hasMethod($method)) { if (!$classDef->extends) { @@ -2743,7 +2780,7 @@ class CompilerBase implements PropertyAccessContext if (!$this->checkAccessible($classDef, $methodDef->flags)) { $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); } - // 函数调用占位符,不是真实的函数调用 + // A function-call placeholder, not a real function call. if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { return false; } @@ -2767,7 +2804,8 @@ class CompilerBase implements PropertyAccessContext $classDef = $this->getClass($class); $originClassDef = $classDef; $constDef = null; - // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 + // Search recursively: if the constant is not defined in the child class, + // try to find it in the parent class. while (true) { if (!$classDef->hasConstant($const)) { if (!$classDef->extends) { @@ -3276,12 +3314,15 @@ class CompilerBase implements PropertyAccessContext } /** - * $GLOBALS['var'] 等价于 global $var; $var ,将字符串常量转为变量名称即可 - * 仅限于字面量字符串可以转为变量名称,其他则使用 php::global() 函数获取 + * Resolve a PHP function name to its native (compiled) name by trying + * every candidate form: absolute names, qualified names resolved through + * the class/namespace import table, unqualified names in the current + * namespace, and `use function` imports. Returns false when no compiled + * function matches. */ protected function findNativeFunction(string $funcName): string|false { - // 绝对命名空间的函数 + // Absolutely-qualified function name. if ($funcName[0] == '\\') { $funcName = ltrim($funcName, '\\'); $possibleFunctionNames = [$this->escapeName($funcName)]; @@ -3759,13 +3800,14 @@ class CompilerBase implements PropertyAccessContext $this->assertNotNativeObjectDynamicClassTarget($expr->class, $expr); } $ctorClassName = ''; - // 匿名类 + // Anonymous class. if ($expr->class instanceof Node\Stmt\Class_) { if ($expr->class->name === null) { $classDef = $expr->class; $className = $this->genAnonClassName(); $classDef->name = new Node\Identifier($className); - // 继承父类和接口可能是 use 的名称,需要转换成全限定名称 + // The inherited parent class and interfaces may be `use` names + // and need to be converted to fully-qualified names. if ($classDef->extends !== null) { $parentClass = $this->getNamespacedClassName($this->parseIdentifier($classDef->extends)); $classDef->extends = new Node\Name\FullyQualified($parentClass); @@ -3777,7 +3819,9 @@ class CompilerBase implements PropertyAccessContext } } $this->flattenEmbeddedClassTraits($classDef); - // 匿名类由根命名空间中的 eval 定义,内部导入的符号必须转为全限定名称。 + // Anonymous classes are defined by eval in the root namespace, + // so symbols imported inside them must be converted to + // fully-qualified names. $this->resolveAnonClassNames($classDef); $this->context->beforeStmtLines[] = 'static THREAD_LOCAL bool ' . $className . '_defined = false;'; $classCode = $this->genEmbeddedCode($classDef); @@ -4144,7 +4188,7 @@ class CompilerBase implements PropertyAccessContext protected function parseEval(Expr\Eval_ $expr): string { $this->assertExprCanBeUsedAsValue($expr->expr, 'eval operand'); - // 对 eval() 指令的 PHP 代码段禁止字面量优化 + // Disable literal-string optimization for the PHP code passed to eval(). $expr->expr->setAttribute('noLiteralString', true); $source = $this->isNativeObjectClass($this->detectClassOfExpr($expr->expr)) ? $this->parseExprToString($expr->expr) @@ -4239,7 +4283,8 @@ class CompilerBase implements PropertyAccessContext } /** - * 左值只能为变量、数组、对象属性、对象静态属性 + * The left value may only be a variable, array element, object property, + * or class static property. */ protected function checkLeftValue(NodeAbstract $expr): void { @@ -4282,7 +4327,8 @@ class CompilerBase implements PropertyAccessContext return $nativePresence; } } - // TypePHP 编译器不允许操作未定义的变量,PHP 的 isset($var) 可能 $var 未定义 + // The TypePHP compiler disallows operating on undefined variables; + // in PHP, isset($var) may be used with an undefined $var. $this->checkVarMustExist($node, $this->parseIdentifier($node)); $fn = $this->getChainedFunc($op); $expr = $node; @@ -4301,7 +4347,7 @@ class CompilerBase implements PropertyAccessContext // $getValue is true: fall through to use the chain+result mechanism, // which ensures the result type is TYPE_VAR (compatible with ternaries). } - // 单属性读取(非链式) + // Single property read (non-chained). if ($this->isPropertyFetch($expr) and $this->isVarExpr($expr->var) and $this->isIdExpr($expr->name)) { $prop = $this->parsePropertyFetch($expr); if ($this->isNativePropertyAccess($expr)) { @@ -4371,7 +4417,8 @@ class CompilerBase implements PropertyAccessContext $node->setAttribute('chainOpResult', $result); return $fn . '(' . $var . ', {' . implode(', ', $list) . '}, ' . $result . ')'; } else { - // toReference(var, {}) 返回空引用,空链时改用成员函数形式 + // toReference(var, {}) returns an empty reference; use the member + // function form instead when the chain is empty. if ($op === self::OP_REFVAL && empty($list)) { return $var . '.toReference()'; } @@ -4854,19 +4901,20 @@ class CompilerBase implements PropertyAccessContext if ($toType === Type::VAR or $fromType === Type::VAR) { return true; } - // 引用当前没有类型信息,按照 var 处理 + // References currently carry no type information, so treat them as var. if ($toType === Type::REF or $fromType === Type::REF) { return true; } - // 类型一致,可以互相赋值 + // Types are identical, so they can be assigned to each other. if ($toType === $fromType) { return true; } - // 原生类型可以互相转换,由 C++ 底层完成 + // Native types can be converted between each other, handled by the C++ layer. if ($this->isNativeType($toType) and $this->isNativeType($fromType)) { return true; } - // BigInt/BigFloat/Decimal 与原生类型之间可能发生隐式转换,允许重新赋值 + // Implicit conversions between BigInt/BigFloat/Decimal and native types + // are possible, so re-assignment is allowed. $bigTypes = [Type::BIGINT, Type::DECIMAL, Type::BIGFLOAT]; if (in_array($toType, $bigTypes, true) or in_array($fromType, $bigTypes, true)) { return true; @@ -4911,12 +4959,12 @@ class CompilerBase implements PropertyAccessContext $scopeClassDef = $this->getClass($this->functionDef->attributeFactoryScope); } } - // 私有方法,只能当前的类使用 + // Private methods can only be used by the current class. if ($flags & Modifiers::PRIVATE) { return $scopeClassDef !== null && $this->isSameClassName($declaringClass, $scopeClassDef->getNamespacedName(false)); } - // 保护方法,只能当前类和子类使用 + // Protected methods can only be used by the current class and its subclasses. if ($flags & Modifiers::PROTECTED) { if (!$scopeClassDef) { return false; @@ -4926,12 +4974,14 @@ class CompilerBase implements PropertyAccessContext $declaringClass ); } - // 类外部调用,只允许调用 public 方法 + // Calls from outside the class are only allowed for public methods. return true; } /** - * 沿继承链查找实际调用的构造函数,包括项目类继承的内部类构造函数。 + * Walk the inheritance chain to find the constructor that is actually + * invoked, including constructors of internal classes inherited by project + * classes. * * @return array{className: string, flags: int}|null */ diff --git a/src/Context/CompilationStateTrait.php b/src/Context/CompilationStateTrait.php index dcf83c2f..84f4f544 100644 --- a/src/Context/CompilationStateTrait.php +++ b/src/Context/CompilationStateTrait.php @@ -45,7 +45,7 @@ trait CompilationStateTrait $this->fatalError($var, 'Duplicate variable `$' . $var->name . '`'); } $this->context->staticVars[$name] = $type; - // 静态变量实际上是一个全局变量的引用 + // A static variable is actually a reference to a global variable. $globalVar = $this->escapeStaticVar($name); $this->addGlobalVar($globalVar, $type); return $globalVar; @@ -165,7 +165,7 @@ trait CompilationStateTrait } /** - * @param string $name 必须传入带有完整命名空间的类名,将会自动转义为 native name + * @param string $name Must be a fully qualified class name including the namespace; it will be automatically escaped to a native name. */ protected function hasFunction(string $name): bool { @@ -222,8 +222,8 @@ trait CompilationStateTrait protected function checkFunction(string $name): void { - // 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误 - // 跳过,稍后再处理 + // The function declaration was detected during the preprocessing stage but is not yet defined, + // meaning it is in the current file but appears in the wrong order. Skip it and handle it later. if (isset($this->symbolDeclInFile[$name]) and $this->symbolDeclInFile[$name] === $this->file and !$this->hasFunction($name)) { diff --git a/src/Entity/FunctionDef.php b/src/Entity/FunctionDef.php index 90aaad82..71aa1b4c 100644 --- a/src/Entity/FunctionDef.php +++ b/src/Entity/FunctionDef.php @@ -64,7 +64,7 @@ class FunctionDef public string $displayName = ''; /** - * @var string 必须是带有命名空间的完整类名 + * @var string Must be a fully qualified class name including the namespace */ public string $returnClass = ''; /** Whether a Native object return may be represented by nullptr. */ diff --git a/src/Extractor.php b/src/Extractor.php index e3238cfb..c9a52335 100644 --- a/src/Extractor.php +++ b/src/Extractor.php @@ -18,12 +18,12 @@ class Extractor } /** - * 提取函数定义. + * Extract function definitions. * - * @param string $filename 文件路径 - * @param array $prefixes 函数名前缀列表 + * @param string $filename File path + * @param array $prefixes List of function-name prefixes * - * @return array 函数列表 + * @return array List of functions */ public function extractFunctions(string $filename, array $prefixes = ['php_']): array { @@ -34,10 +34,10 @@ class Extractor $this->info("分析文件: {$filename}"); $this->info('函数前缀: ' . implode(', ', $prefixes)); - // 运行 ctags + // Run ctags. $tags = $this->runCtags($filename); - // 过滤和解析函数 + // Filter and parse functions. $functions = []; foreach ($tags as $tag) { if ($tag['kind'] !== 'function') { @@ -46,7 +46,7 @@ class Extractor $funcName = $tag['name'] ?? ''; - // 检查前缀 + // Check the prefix. $matched = false; foreach ($prefixes as $prefix) { if (str_starts_with($funcName, $prefix)) { @@ -59,7 +59,7 @@ class Extractor continue; } - // 解析函数详细信息 + // Parse the detailed function information. $funcInfo = $this->parseFunction($filename, $tag); if ($funcInfo) { $functions[] = $funcInfo; @@ -72,7 +72,7 @@ class Extractor } /** - * 批量提取多个文件. + * Extract functions from multiple files in bulk. */ public function extractFromFiles(array $files, array $prefixes = ['php_']): array { @@ -91,7 +91,7 @@ class Extractor } /** - * 检查 ctags 是否可用. + * Check whether ctags is available. */ private function checkCtags(): void { @@ -107,7 +107,7 @@ class Extractor } /** - * 运行 ctags 命令. + * Run the ctags command. */ private function runCtags(string $filename): array { @@ -123,7 +123,7 @@ class Extractor throw new \RuntimeException('ctags 执行失败'); } - // 解析 JSON 输出 + // Parse the JSON output. $tags = []; $lines = explode("\n", trim($output)); @@ -144,7 +144,7 @@ class Extractor } /** - * 解析单个函数的详细信息. + * Parse the detailed information of a single function. */ private function parseFunction(string $filename, array $tag): ?array { @@ -155,17 +155,17 @@ class Extractor return null; } - // 提取完整的函数签名 + // Extract the complete function signature. $signature = $this->extractSignature($filename, $lineNum, $funcName); if (empty($signature)) { return null; } - // 解析返回类型 + // Parse the return type. $returnType = $this->parseReturnType($signature, $funcName); - // 解析参数 + // Parse the parameters. $parameters = $this->parseParameters($signature, $funcName); return [ @@ -183,7 +183,7 @@ class Extractor } /** - * 从源文件中提取完整的函数签名. + * Extract the complete function signature from the source file. */ private function extractSignature(string $filename, int $lineNum, string $funcName): string { @@ -193,7 +193,7 @@ class Extractor return ''; } - // 从函数声明行开始收集,直到遇到 { 或 ; + // Collect lines starting from the function declaration until a { or ; is reached. $signatureLines = []; $maxLines = min($lineNum + 20, count($lines)); @@ -201,37 +201,37 @@ class Extractor $line = $lines[$i]; $signatureLines[] = $line; - // 检查是否到达函数体或声明结束 + // Check whether the function body or the end of the declaration has been reached. if (strpos($line, '{') !== false || strpos($line, ';') !== false) { break; } } - // 合并并清理 + // Join and clean up. $signature = implode(' ', $signatureLines); - // 移除 { 或 ; 之后的内容 + // Remove everything after the { or ;. $signature = preg_replace('/[{;].*$/', '', $signature); - // 合并多个空白字符 + // Collapse multiple whitespace characters. $signature = preg_replace('/\s+/', ' ', $signature); - // 清理首尾空白 + // Trim leading and trailing whitespace. return trim($signature); } /** - * 解析返回类型. + * Parse the return type. */ private function parseReturnType(string $signature, string $funcName): string { - // 匹配: <返回类型> <函数名>( + // Match: ( $pattern = '/^(.+?)\s+' . preg_quote($funcName, '/') . '\s*\(/'; if (preg_match($pattern, $signature, $matches)) { $returnType = trim($matches[1]); - // 移除可能的修饰符 + // Remove possible modifiers. $returnType = preg_replace('/\b(static|inline|extern|virtual|explicit)\b/', '', $returnType); $returnType = preg_replace('/\s+/', ' ', $returnType); $returnType = trim($returnType); @@ -243,11 +243,11 @@ class Extractor } /** - * 解析参数列表. + * Parse the parameter list. */ private function parseParameters(string $signature, string $funcName): array { - // 提取括号内的参数 + // Extract the parameters inside the parentheses. $pattern = '/' . preg_quote($funcName, '/') . '\s*\((.*?)\)/s'; if (!preg_match($pattern, $signature, $matches)) { @@ -256,12 +256,12 @@ class Extractor $paramsStr = trim($matches[1]); - // 空参数或 void + // Empty parameters or void. if (empty($paramsStr) || $paramsStr === 'void') { return []; } - // 分割参数(处理嵌套的模板和括号) + // Split the parameters (handling nested templates and parentheses). $params = $this->splitParameters($paramsStr); $parameters = []; @@ -282,7 +282,7 @@ class Extractor } /** - * 智能分割参数(处理嵌套的模板和括号). + * Intelligently split parameters (handling nested templates and parentheses). */ private function splitParameters(string $paramsStr): array { @@ -316,17 +316,17 @@ class Extractor } /** - * 解析单个参数. + * Parse a single parameter. */ private function parseParameter(string $param): ?array { $param = trim($param); - // 移除默认值 + // Remove the default value. $param = preg_replace('/\s*=\s*.*$/', '', $param); - // 尝试匹配: <类型> <名称> - // 支持复杂类型如: const char*, std::string&, int**, etc. + // Try to match: . + // Supports complex types such as: const char*, std::string&, int**, etc. if (preg_match('/^(.+?)\s+(\w+)\s*$/', $param, $matches)) { return [ 'type' => trim($matches[1]), @@ -334,7 +334,7 @@ class Extractor ]; } - // 只有类型,没有名称 + // Only a type, no name. return [ 'type' => $param, 'name' => '', @@ -342,7 +342,7 @@ class Extractor } /** - * 输出信息. + * Output an informational message. */ private function info(string $message): void { @@ -350,7 +350,7 @@ class Extractor } /** - * 输出警告. + * Output a warning. */ private function warn(string $message): void { @@ -358,7 +358,7 @@ class Extractor } /** - * 输出错误并退出. + * Output an error and exit. */ private function error(string $message): void { diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index 6e19fc8f..03c70f44 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -35,7 +35,7 @@ trait CallArgumentGenerator $hasNamedArg = false; $argNameIndex = $this->getFunctionArgNameIndex($functionDef); $variadicArgIndex = $this->getVariadicArgIndex($functionDef); - // 对命名参数进行重排 + // Reorder the named arguments into their declared positions foreach ($callArgs as $i => $arg) { if ($this->isPlaceholderExpr($arg)) { throw new PlaceHolder(); @@ -76,7 +76,7 @@ trait CallArgumentGenerator if ($deferTrailingDefaults && $variadicArgCount > 0) { $lastProvidedIndex = $variadicArgIndex; } - // 命名参数中间存在空洞,需要使用默认参数填充 + // Holes left between named arguments must be filled with default arguments foreach ($functionDef->argInfoList as $k => $argInfo) { if ($k < $parameterOffset) { continue; @@ -118,7 +118,8 @@ trait CallArgumentGenerator } } - // 函数只接受一个变长参数,且调用参数为空,直接传入空数组 + // If the function only accepts a single variadic parameter and the call + // supplies no arguments, pass an empty array directly if (count($sourceArgs) === 0 and count($functionDef->argInfoList) === $parameterOffset + 1 and $functionDef->argInfoList[$parameterOffset]->variadic) { @@ -203,7 +204,8 @@ trait CallArgumentGenerator } if ($className) { - // 动态调用类方法,无法判断参数是否为引用 + // For dynamically called class methods, whether the parameter is + // passed by reference cannot be determined if ($className === self::DYNAMIC_CALLED_CLASS) { return false; } @@ -216,7 +218,8 @@ trait CallArgumentGenerator return $param->isPassedByReference(); } - // 参数索引超出声明范围,检查最后一个参数是否为变长引用参数(如 &...$rest) + // The argument index exceeds the declared range; check whether the last + // parameter is a by-reference variadic parameter (e.g. &...$rest) $variadicParam = Reflection::getVariadicParameter($funcName, $className); return $variadicParam !== null && $variadicParam->isPassedByReference(); } @@ -492,7 +495,7 @@ trait CallArgumentGenerator $array = $this->parseIdentifier($arg->value->var); if ($array === 'GLOBALS') { $globalVar = $this->parseGlobalsArrayDimFetch($arg->value); - // 全局变量作为引用参数 + // Global variable passed as a by-reference argument if ($byRef) { $ref = $this->addTmpVar(Type::REF); $this->context->beforeStmtLines[] = $ref . ' = ' . $globalVar . '.toReference();'; @@ -749,8 +752,9 @@ trait CallArgumentGenerator } /** - * 展开 refval() 调用中的数组元素或对象属性,返回对应的 C++ 引用表达式。 - * 若为普通变量则返回 null,由调用方自行处理。 + * Expand an array element or object property inside a refval() call into its + * corresponding C++ reference expression. Returns null for a plain variable, + * which the caller then handles itself. */ protected function expandRefvalExpr(NodeAbstract $inner, Node\Arg $arg): ?string { @@ -777,27 +781,31 @@ trait CallArgumentGenerator } /** - * 仅用于动态调用的参数解析 + * Argument parsing used only for dynamic calls */ protected function parseArgRefVar(Node\Arg $arg, string $name): string { if (!$this->hasVar($name)) { - // 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用 + // For a by-reference parameter, an undefined variable may be passed; + // it is created immediately as a reference $this->addLocalVar($name, Type::REF); } elseif ($this->getVarType($name) === Type::REF) { return '&' . $name; } else { - // 本地变量,且是原生类型,则转为普通变量 + // A local variable of native type is converted to a plain variable if ($this->hasLocalVar($name) and $this->isNativeType($this->getVarType($name))) { $this->context->localVars[$name] = Type::VAR; } - // 需要引用类型的参数,使用临时变量作为引用,并替换掉实际的参数 + // For a by-reference parameter, use a temporary variable as the reference + // and replace the actual argument with it $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, Type::REF); $this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($arg->value) . '.toReference();'; $name = $tmpVar; } - // 动态调用,参数列表是 Variant 类型而不是 Reference,必须使用 & 符号取地址,传递指针,以保持引用传递 + // For dynamic calls, the argument list is Variant rather than Reference, + // so the & operator must be used to take the address and pass a pointer + // in order to preserve pass-by-reference semantics return '&' . $name; } diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index 99428229..df24d589 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -285,7 +285,8 @@ trait ClosureGenerator $this->fatalError($useItem->var, 'Incorrect Closure use syntax, only variable names are allowed'); } if ($useItem->byRef) { - // 闭包的 use 语法,若为引用类型,可以就地创建变量 + // For a closure use clause, a by-reference capture may create + // the variable in place if it does not exist yet if (!isset($oriContext->localVars[$var]) && !isset($oriContext->staticVars[$var])) { $oriContext->localVars[$var] = Type::REF; diff --git a/src/Generator/ResourceFileGenerator.php b/src/Generator/ResourceFileGenerator.php index a663fb5a..7a1e8e96 100644 --- a/src/Generator/ResourceFileGenerator.php +++ b/src/Generator/ResourceFileGenerator.php @@ -3,11 +3,12 @@ namespace TypePhp\Generator; /** - * Windows 资源文件 (.rc) 生成器 + * Windows resource file (.rc) generator * - * 用于生成 Windows PE 资源文件,可将图标、版本信息等嵌入到 exe 中 + * Generates Windows PE resource files, embedding icons, version information, + * and other resources into the exe * - * 配置示例(在 project.yml 中): + * Configuration example (in project.yml): * * resource: * icon: path/to/icon.ico @@ -28,18 +29,18 @@ namespace TypePhp\Generator; * product-name: "My Product" * comments: "Built with TypePHP" * - * # manifest 与 resource 同级(可选,Windows 平台缺省不携带) + * # manifest sits at the same level as resource (optional; omitted by default on Windows) * manifest: path/to/app.manifest */ class ResourceFileGenerator { /** - * 资源配置 + * Resource configuration */ private array $config; /** - * 项目目录(用于解析相对路径) + * Project directory (used to resolve relative paths) */ private string $projectDir; @@ -50,7 +51,7 @@ class ResourceFileGenerator } /** - * 检查是否有任何资源配置 + * Check whether any resource is configured */ public function hasResource(): bool { @@ -60,7 +61,7 @@ class ResourceFileGenerator } /** - * 获取图标文件的绝对路径 + * Get the absolute path of the icon file */ public function getIconPath(): ?string { @@ -73,7 +74,7 @@ class ResourceFileGenerator } /** - * 获取 manifest 文件的绝对路径 + * Get the absolute path of the manifest file */ public function getManifestPath(): ?string { @@ -86,21 +87,21 @@ class ResourceFileGenerator } /** - * 解析相对/绝对路径为绝对路径 + * Resolve a relative/absolute path into an absolute path */ private function resolvePath(string $path): string { - // 如果是绝对路径,直接使用 + // If it is already an absolute path, use it as-is if (preg_match('/^[A-Za-z]:\\\\|^\//', $path)) { return $path; } - // 相对路径,基于项目目录解析 + // Otherwise resolve the relative path against the project directory return $this->projectDir . DIRECTORY_SEPARATOR . $path; } /** - * 生成 .rc 资源文件内容 + * Generate the contents of the .rc resource file */ public function generate(): string { @@ -109,15 +110,15 @@ class ResourceFileGenerator $content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL; $content .= PHP_EOL; - // 告诉 rc.exe 此文件使用 UTF-8 编码,避免中文乱码 + // Tell rc.exe this file is UTF-8 encoded to avoid garbled Chinese text $content .= '#pragma code_page(65001)' . PHP_EOL; $content .= PHP_EOL; - // 包含 Windows 版本信息头文件 + // Include the Windows header for version information $content .= '#include ' . PHP_EOL; $content .= PHP_EOL; - // Manifest 资源(Windows 清单文件,如 UAC、DPI 感知等) + // Manifest resource (Windows application manifest, e.g. UAC, DPI awareness, etc.) $manifestPath = $this->getManifestPath(); if ($manifestPath) { $manifestPathRc = str_replace('\\', '/', $manifestPath); @@ -126,17 +127,17 @@ class ResourceFileGenerator $content .= PHP_EOL; } - // 图标资源 + // Icon resource $iconPath = $this->getIconPath(); if ($iconPath) { - // 使用正斜杠,Windows RC 编译器更兼容 + // Use forward slashes for better compatibility with the Windows RC compiler $iconPathRc = str_replace('\\', '/', $iconPath); $content .= '// Icon Resource' . PHP_EOL; $content .= 'MAINICON ICON "' . addslashes($iconPathRc) . '"' . PHP_EOL; $content .= PHP_EOL; } - // 版本信息 + // Version information $versionInfo = $this->config['version-info'] ?? []; if (!empty($versionInfo)) { $content .= $this->generateVersionInfo($versionInfo); @@ -146,7 +147,7 @@ class ResourceFileGenerator } /** - * 生成版本信息块 + * Generate the version information block */ private function generateVersionInfo(array $info): string { @@ -165,16 +166,17 @@ class ResourceFileGenerator $content .= 'FILESUBTYPE ' . ($info['file-subtype'] ?? 'VFT2_UNKNOWN') . PHP_EOL; $content .= 'BEGIN' . PHP_EOL; - // StringFileInfo 块 + // StringFileInfo block $content .= ' BLOCK "StringFileInfo"' . PHP_EOL; $content .= ' BEGIN' . PHP_EOL; - // 语言代码页(040904b0 = 英文/UTF-8,配合 #pragma code_page(65001) 正确显示中文) + // Language code page (040904b0 = English/UTF-8, used with #pragma code_page(65001) + // so Chinese text renders correctly) $langCodepage = $info['lang-codepage'] ?? '040904b0'; $content .= ' BLOCK "' . $langCodepage . '"' . PHP_EOL; $content .= ' BEGIN' . PHP_EOL; - // 字符串值 + // String values $stringFields = [ 'company-name' => 'CompanyName', 'file-description' => 'FileDescription', @@ -195,19 +197,20 @@ class ResourceFileGenerator } } - // 如果没有设置 FileVersion,从 file-version 字段自动填入 + // If FileVersion was not set, it is auto-filled from the file-version field if (!isset($info['file-version-str']) && $fileVersion) { - // 已在上面通过 file-version 键处理 + // Already handled above via the file-version key } $content .= ' END' . PHP_EOL; $content .= ' END' . PHP_EOL; - // VarFileInfo 块 + // VarFileInfo block $content .= ' BLOCK "VarFileInfo"' . PHP_EOL; $content .= ' BEGIN' . PHP_EOL; - // 0x0409 = English(US),1200 = Unicode(UTF-16) - // 配合 StringFileInfo 中的 040904b0 代码页,确保中文在 UTF-8 源文件中正确编码 + // 0x0409 = English(US), 1200 = Unicode(UTF-16) + // Combined with the 040904b0 code page in StringFileInfo, this ensures + // Chinese text in the UTF-8 source file is encoded correctly $content .= ' VALUE "Translation", 0x0409, 1200' . PHP_EOL; $content .= ' END' . PHP_EOL; @@ -217,31 +220,31 @@ class ResourceFileGenerator } /** - * 将版本号格式化为逗号分隔的格式(1,0,0,0) - * 支持以下输入格式: + * Format a version number into comma-separated form (1,0,0,0). + * Supported input formats: * - "1.0.0.0" → "1,0,0,0" * - "1,0,0,0" → "1,0,0,0" - * - "v1052" → "1052,0,0,0" (去掉 v 前缀) + * - "v1052" → "1052,0,0,0" (strips the leading "v") * - "1.0" → "1,0,0,0" */ private function formatVersionDots(string $version): string { - // 去掉 v/V 前缀(如 v1052 → 1052) + // Strip a leading v/V prefix (e.g. v1052 → 1052) $version = ltrim($version, 'vV'); - // 如果已经是逗号分隔格式,直接返回 + // Return as-is if it is already comma-separated if (str_contains($version, ',')) { return $version; } - // 用点号分隔 + // Split on dots $parts = explode('.', $version); - // 确保每个部分都是数字(过滤掉非数字字符) + // Keep only digits in each part (filter out non-numeric characters) foreach ($parts as $i => $part) { $parts[$i] = preg_replace('/[^0-9]/', '', $part) ?: '0'; } - // 确保恰好有4个部分 + // Ensure exactly four parts while (count($parts) < 4) { $parts[] = '0'; } @@ -250,7 +253,7 @@ class ResourceFileGenerator } /** - * 生成 resource.h 头文件内容(可选,供 C++ 代码引用资源 ID) + * Generate the resource.h header contents (optional, lets C++ code reference resource IDs) */ public function generateHeader(): string { diff --git a/src/Installer/LibPhpInstaller.php b/src/Installer/LibPhpInstaller.php index 2f082bf4..1c2e7e55 100644 --- a/src/Installer/LibPhpInstaller.php +++ b/src/Installer/LibPhpInstaller.php @@ -176,10 +176,11 @@ final class LibPhpInstaller private function currentConfigureOptions(): string { - // 优先使用 PHP_BINARY -i 的 Configure Command:输出保留每个参数的 - // 引号,能正确处理 `CFLAGS=-g -O2` 这类含空格的值。而 - // `php-config --configure-options` 会丢失引号,导致含空格的值被 - // 错误拆分(例如 `-O2` 被当作独立参数传给 configure)。 + // Prefer the "Configure Command:" output from `PHP_BINARY -i`, which preserves + // the quoting of each argument and can therefore handle values containing spaces + // such as `CFLAGS=-g -O2`. In contrast, `php-config --configure-options` drops the + // quotes, causing space-containing values to be split incorrectly (for example, + // `-O2` being passed to configure as a separate argument). $info = $this->capture([PHP_BINARY, '-n', '-i']); if (preg_match('/^Configure Command =>\s*(.+)$/mi', $info, $match)) { $words = PhpBuildConfiguration::parseShellWords(trim($match[1])); @@ -189,8 +190,9 @@ final class LibPhpInstaller return implode(' ', array_map('escapeshellarg', $words)); } - // 后备:php-config --configure-options。PPA 的多版本 PHP 共用 - // /usr 前缀,因此 PHP_HOME=/usr 时必须优先 php-config8.x。 + // Fallback: php-config --configure-options. On PPA multi-version installations + // several PHP versions share the /usr prefix, so when PHP_HOME=/usr the + // versioned php-config8.x must be preferred. $versionedPhpConfig = $this->sourcePhpDir . '/bin/php-config' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; if ($this->sourcePhpDir !== null && is_executable($versionedPhpConfig)) { diff --git a/src/Metadata/Constants.php b/src/Metadata/Constants.php index c2da960f..0e796950 100644 --- a/src/Metadata/Constants.php +++ b/src/Metadata/Constants.php @@ -188,7 +188,7 @@ class Constants 'description' => 'Run the compiled binary after build', 'noValue' => true, ], - // 内部开发选项,用于定位特定行的翻译问题,请勿写入用户文档 + // Internal development option used to locate translation issues on a specific line. Do not write it into user documentation. 'debug-line' => [ 'longPrefix' => 'debug-line', 'description' => 'Enable debug line', @@ -305,10 +305,10 @@ class Constants ]; /** - * MSVC 编译器警告屏蔽列表 - * 这些警告来自 Windows SDK 和 PHP SDK 头文件,都是编译器噪音,不影响功能 + * MSVC compiler warning suppression list. + * These warnings come from Windows SDK and PHP SDK headers and are compiler noise that does not affect functionality. * - * @var array 键为警告编号,值为说明 + * @var array key is the warning number, value is the description */ public const array MSVC_SUPPRESSED_WARNINGS = [ '4244' => '类型转换可能丢失数据 (int -> smaller type)', diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index f78f2ff9..fe2ae690 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -233,8 +233,9 @@ trait FuncCallOptimizer } } - // 检测参数中使用的变量是否已定义,若变量不存在则回退到动态调用路径 - // 动态路径中的 parseCallArgs() 会给出明确的错误信息 + // Check whether the variables used in the arguments are defined; if a variable + // does not exist, fall back to the dynamic call path, where parseCallArgs() + // produces a clear error message. foreach ($expr->args as $arg) { if (!$arg instanceof Node\Arg) { continue; @@ -269,7 +270,7 @@ trait FuncCallOptimizer protected function dispatchFuncCall(string $name, Node\Expr\FuncCall $expr, array $config): string|false { - // 命名参数 / unpack(...)展开需要运行时处理,回退到动态调用路径 + // Named arguments and unpack (...) expansion require runtime handling; fall back to the dynamic call path. foreach ($expr->args as $arg) { if ($arg->name !== null || $arg->unpack) { return false; diff --git a/src/Parser/ArrayExpressionTrait.php b/src/Parser/ArrayExpressionTrait.php index c0486b80..36fda31b 100644 --- a/src/Parser/ArrayExpressionTrait.php +++ b/src/Parser/ArrayExpressionTrait.php @@ -18,7 +18,7 @@ trait ArrayExpressionTrait protected function parseArray(Expr\Array_ $node): string { $items = $node->items; - // 优化代码风格,空数组直接返回{},否则会产生一些空洞内容 + // Optimize code style: return {} directly for an empty array, otherwise it would produce empty entries if (count($items) === 0) { return Type::ARRAY . '{}'; } @@ -55,7 +55,7 @@ trait ArrayExpressionTrait } } - // 存在混合键,则需要拆分为多行插入 + // Mixed keys are present, so split the insertion into multiple statements if ($hasReference or $hasUnpack or $hasVarKey or ($hasNextInsert && $hasKey) or ($hasIntKey and $hasStrKey)) { return $this->parseArrayMixed($node); } @@ -82,7 +82,8 @@ trait ArrayExpressionTrait } /** - * 获取包含路径 + * Resolve a `$GLOBALS[...]` array-dim fetch to its static slot when the key + * is a known global name, or to a php::global() lookup otherwise. */ protected function parseGlobalsArrayDimFetch(Expr\ArrayDimFetch $node): string @@ -270,7 +271,7 @@ trait ArrayExpressionTrait { $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, Type::ARRAY); - // 释放临时变量,避免修改数组产生数组复制操作 + // Release the temporary variable to avoid array copies when the array is modified $this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.clean();'; $items = $node->items; diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index d0aca8a6..470490d1 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -153,7 +153,7 @@ trait AssignOpTrait $this->addLocalVar($tmpVar, Type::VAR); } - // 翻转赋值链 + // Reverse the assignment chain $chain = array_reverse($chain); $list = []; @@ -525,7 +525,7 @@ trait AssignOpTrait return $copyAssign; } } - // 类型推断,获取对象的类名,如果不是对象则返回空字符串 + // Infer the type and obtain the object's class name; return an empty string for non-objects $rightClass = $this->detectClassOfExpr($right); $markNativeObjectNonNull = $this->context->scopeLevel <= 1 && !$this->hasScopeGlobalVar($var) @@ -548,7 +548,7 @@ trait AssignOpTrait $this->fatalError($right, "Cannot assign native object `{$rightClass}` to `{$leftClass}`"); } } - // 右值是一个对象,已获得类的名称,左值必须与右值的类一致 + // The right-hand value is an object and its class name is known; the left-hand side must match the right-hand side's class if ($rightClass) { if (!$this->hasVar($var)) { if ($this->isNativeObjectClass($rightClass)) { @@ -641,7 +641,7 @@ trait AssignOpTrait } } } - // 变量第一次被赋值,确定其类型,由于 PHP 的变量作用域是 function 级的,在 for/while 块中声明的变量,可以在块外使用 + // On first assignment the variable's type is determined. PHP variable scope is function-level, so variables declared in for/while blocks remain usable outside the block if (!$this->hasVar($var)) { $finalVarType = $this->getNormalAssignType($type); $finalVarType = $this->isNativeType($finalVarType) ? $this->getNativeType($finalVarType) : $finalVarType; @@ -944,9 +944,9 @@ trait AssignOpTrait } /** * $count[$r] -= 1; - * 需要转为下面语句: + * must be lowered to the following statements: * $tmp_var = $count[$r] - 1; - * $count[$r] = $tmp_var;. + * $count[$r] = $tmp_var; */ $isGlobals = $this->isVarExpr($node->var->var) && $node->var->var->name === 'GLOBALS'; $type = $isGlobals ? Type::VAR : $this->detectVarType($node->var); diff --git a/src/Parser/BinaryOpTrait.php b/src/Parser/BinaryOpTrait.php index 3212cb40..a97f653c 100644 --- a/src/Parser/BinaryOpTrait.php +++ b/src/Parser/BinaryOpTrait.php @@ -24,7 +24,7 @@ trait BinaryOpTrait $this->assertExprCanBeUsedAsValue($left, 'binary operand'); $this->assertExprCanBeUsedAsValue($right, 'binary operand'); - // 运算逻辑,优先转为数字 + // Arithmetic logic: convert to a numeric type first when possible $leftExpr = $this->parseOrderedBinaryOperand($left); $rightExpr = $this->parseOrderedBinaryOperand($right); @@ -927,7 +927,7 @@ trait BinaryOpTrait protected function parseCompareExpr(NodeAbstract $expr): string { $this->assertExprCanBeUsedAsValue($expr, 'comparison operand'); - // PHPX 与 bool 值比较会出现重载错误,所以需要转换成 bool 值 + // Comparing PHPX values with bool causes an overload error, so convert them to bool first if ($this->isScalarBool($expr)) { return $this->getBoolValue($expr); } diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index 5fc77a9e..235157de 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -220,8 +220,8 @@ trait ForeachTrait } /** - * 为了兼容已有代码,默认不使用原生类型,而是将整数和浮点数作为 php 变量处理 - * 原生 int/float/bool 类型,是不支持自动转换的,例如如果 int 计算超过最大值后,会自动转为 float,除法若不能除尽,则会转为 float - * 某些情况下高性能计算,可能需要使用原生类型,使用 $a = std::int(0) 来显式地使用原生类型 + * For backward compatibility, native types are not used by default; integers and floats are treated as php variables. + * Native int/float/bool types do not support automatic conversion. For example, an int computation that exceeds its maximum value is promoted to float, and a division that does not divide evenly becomes float. + * In some cases high-performance computation may need native types; use `$a = std::int(0)` to explicitly opt into native types. */ } diff --git a/src/Parser/FunctionCallTrait.php b/src/Parser/FunctionCallTrait.php index c3ef736d..1b573516 100644 --- a/src/Parser/FunctionCallTrait.php +++ b/src/Parser/FunctionCallTrait.php @@ -177,7 +177,7 @@ trait FunctionCallTrait ) { $this->fatalError($expr, 'Native ABI functions cannot be converted to Zend closures'); } - // 函数调用占位符,不是真实的函数调用 + // Function call placeholder, not a real function call if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { return $this->genPlaceHolder($this->identifierToStr($expr->name)); } @@ -195,7 +195,7 @@ trait FunctionCallTrait return $this->genPlaceHolder($this->identifierToStr($expr->name)); } } - // 动态调用的函数,转换函数名为带有命名空间的全限定名称 + // For dynamically dispatched functions, convert the function name to its fully qualified name including the namespace $name = $this->getNamespacedFuncName($name); $this->checkInternalFunctionArgCount($name, $expr); $code = $this->parseFuncCallWithOptimizer($name, $expr); diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php index 959123fc..0f6ec71e 100644 --- a/src/Parser/MethodCallTrait.php +++ b/src/Parser/MethodCallTrait.php @@ -252,7 +252,7 @@ trait MethodCallTrait } $nativeFunc = $this->getNativeMethod($expr, $class, $method); - // 存在 Native 类,但是没有找到方法,可能是动态调用 + // A Native class exists but the method was not found; this may be a dynamic call if (!$nativeFunc) { if ($this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) { throw new DynamicCall(); @@ -261,7 +261,7 @@ trait MethodCallTrait $fullMethodName = $this->getOverrideMethodName($class, $method); - // 存在子类同名方法,尝试去虚化 + // A subclass declares a method with the same name, so try to devirtualize if ($this->isOverrideMethod($fullMethodName)) { if (!$this->canDevirtualize($object, $class, $method)) { return false; @@ -414,7 +414,7 @@ trait MethodCallTrait if (empty($expr->args)) { return 'this_.call(' . $methodPtr . ')'; } - // 传入方法名与父类名,以便在按引用参数检测时解析方法签名 + // Pass the method name and parent class so the method signature can be resolved when detecting by-reference arguments return 'this_.call(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args, $method, $parentClass) . ')'; } @@ -465,10 +465,10 @@ trait MethodCallTrait if ($this->isTypedObject($object)) { $class = $this->getObjectType($object); } elseif ($object === 'this_') { - // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 + // $this is statically typed as the current class inside a constructor/method, so abstract methods and other by-reference parameter signatures can be resolved $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; } else { - // 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。 + // Variables of interface or abstract-class type have no concrete object type, but by-reference parameters can still be resolved from the declared signature. $class = $this->getDeclaredObjectType($object); } } @@ -591,7 +591,7 @@ trait MethodCallTrait . $this->parseCallArgs($expr->args) . ')'; } - // 可转为原生调用的 MethodCall + // Method calls that can be lowered to a native call if (($this->isVarExpr($expr->var) || $materializedNativeReceiver) and $this->isNamedMethod($expr->name)) { $type = $this->getVarType($object); if ($class !== '' && $this->isNativeObjectClass($class)) { @@ -647,9 +647,9 @@ trait MethodCallTrait return self::PREFIX . $nativeFunc . '(' . $receiver . ', ' . $this->parseNativeCallArgs($expr->args, $nativeFunc) . ')'; } - // 引用参数允许方法调用:有class信息走原生调用,无class信息走动态调用 + // Method calls are allowed on references: use a native call when class info is available, otherwise a dynamic call if (!$this->checkArgType($type, Type::OBJECT) and $type !== Type::REF) { - // 非对象类型可使用内置方法 + // Non-object types can use built-in methods $fn = $this->findUniversalMethodAnyType($type, $methodName); if ($fn) { if ($type === Type::STREAM) { @@ -705,7 +705,7 @@ trait MethodCallTrait } } - // 表达式返回值也可使用内置方法:fn()->method(), $obj->fn()->method(), Foo::fn()->method(), $obj->prop->method() + // Expression results can also use built-in methods: fn()->method(), $obj->fn()->method(), Foo::fn()->method(), $obj->prop->method() if (!$this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { $type = $this->detectTypeOfExpr($expr->var); if ($type === Type::VOID) { @@ -929,7 +929,7 @@ trait MethodCallTrait ); } $placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]); - // 用于在按引用参数检测时解析方法签名(late static binding 在当前类层级中解析) + // Used to resolve the method signature when detecting by-reference arguments (late static binding is resolved within the current class hierarchy) $rtFunc = $method; $rtClass = $this->getFullClassName(); } else { @@ -974,7 +974,7 @@ trait MethodCallTrait } catch (PlaceHolder) { return $this->genPlaceHolder($this->genArray($callScope)); } - // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 + // When a method definition calls a current-class method via self::method(), the this_ pointer must still be passed if ($this->methodDef and $self) { $object = 'this_'; } else { diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index f821b092..c553fd09 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -595,13 +595,13 @@ trait PropertyAccessTrait } if ($def->class === '' or $this->isAbstractClass($def->class) or $this->isInterface($def->class) or !$this->hasClass($def->class)) { - // 属性 declared class 若是接口、抽象类或动态类,当前属性布局优化无法静态确认最终对象类型。 - // 不在这里 fatal;后续 wrapObjectPropertyAssignTypeCheck() 会在需要时插入运行时检查。 + // If the property's declared class is an interface, abstract class, or dynamic class, the current property layout optimization cannot statically determine the final object type. + // Do not report a fatal error here; wrapObjectPropertyAssignTypeCheck() inserts a runtime check later when needed. return; } $rightClass = $this->detectClassOfExpr($right); - // TODO 静态编译阶段无法获得准确的类型,需要在运行时检查 + // TODO: the exact type cannot be determined at the static compilation stage; a runtime check is required if ($rightClass === '') { return; } diff --git a/src/Parser/SwitchTrait.php b/src/Parser/SwitchTrait.php index 794391c1..b851b54a 100644 --- a/src/Parser/SwitchTrait.php +++ b/src/Parser/SwitchTrait.php @@ -38,7 +38,7 @@ trait SwitchTrait $var_def .= $type . ' ' . $tmp_var . ' = ' . $condExpr . ';' . PHP_EOL; $var_def .= $this->formatCapturedStmtLines($condAfterStmts); - // 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置 + // Save the scope; switch parsing may fail partway and add variables in the process, so it must be reset $localVars = $this->context->localVars; $code = $this->parseBeforeStmtLines() . PHP_EOL; diff --git a/src/Platform/PlatformBase.php b/src/Platform/PlatformBase.php index 10a1d678..91dd8e91 100644 --- a/src/Platform/PlatformBase.php +++ b/src/Platform/PlatformBase.php @@ -3,98 +3,98 @@ namespace TypePhp\Platform; /** - * 平台抽象基类 - * 定义所有平台必须实现的接口 + * Abstract platform base class. + * Defines the interface every platform must implement. */ abstract class PlatformBase { /** - * 获取平台名称 + * Get the platform name. */ abstract public function getName(): string; /** - * 判断是否为当前平台 + * Determine whether this is the current platform. */ abstract public function isCurrent(): bool; /** - * 获取编译器包含路径参数 + * Get the compiler include-path flags. */ abstract public function getIncludeFlags(array $includePaths): string; /** - * 获取链接器库路径参数 + * Get the linker library-path flags. */ abstract public function getLibraryPathFlags(array $libraryPaths): string; /** - * 获取链接库参数 + * Get the link-library flags. */ abstract public function getLibraryFlags(array $libraries): string; /** - * 获取文件扩展名 + * Get the object file extension. */ abstract public function getObjectExtension(): string; /** - * 获取可执行文件扩展名 + * Get the executable file extension. */ abstract public function getExecutableExtension(): string; /** - * 获取动态库扩展名 + * Get the shared library extension. */ abstract public function getSharedLibraryExtension(): string; /** - * 获取生成共享库所需的链接器选项 + * Get the linker options required to produce a shared library. */ abstract public function getSharedLinkFlag(): string; /** - * 获取无控制台程序的子系统选项;不适用的平台返回空字符串 + * Get the subsystem options for a console-less program; platforms where this does not apply return an empty string. */ abstract public function getSubsystemOptions(bool $noConsole): string; /** - * 获取平台 C 运行库链接配置;不适用的平台返回空字符串 + * Get the platform C runtime library link configuration; platforms where this does not apply return an empty string. */ abstract public function getCrtConfig(): string; /** - * 获取路径分隔符 + * Get the path separator. */ abstract public function getPathSeparator(): string; /** - * 获取该平台默认使用的 C++ 编译器命令 + * Get the default C++ compiler command for this platform. */ abstract public function getDefaultCompiler(): string; /** - * 获取 PHP 安装目录 + * Get the PHP installation directory. */ abstract public function getPhpDir(): string; /** - * 构建 PHP 包含路径 + * Build the PHP include paths. */ abstract public function buildPhpIncludePaths(string $phpDir): array; /** - * 构建 PHP 库路径 + * Build the PHP library paths. */ abstract public function buildPhpLibPaths(string $phpDir): array; /** - * 检测 PHP 库文件 + * Detect the PHP library files. */ abstract public function detectPhpLibs(string $phpDir): array; /** - * 获取指定构建模式的目标文件扩展名 + * Get the target file extension for the given build mode. */ public function getTargetExtension(string $buildMode): string { @@ -104,7 +104,7 @@ abstract class PlatformBase } /** - * 获取构建前的运行库检查告警 + * Get the runtime library check warnings issued before building. */ public function getBuildLibraryWarnings( string $phpDir, @@ -141,7 +141,7 @@ abstract class PlatformBase } /** - * 当前平台是否适合使用 pcntl_fork 并行编译 + * Whether this platform is suitable for parallel compilation using pcntl_fork. */ public function supportsPcntlParallelCompile(): bool { @@ -154,7 +154,7 @@ abstract class PlatformBase } /** - * 规范化路径 + * Normalize a path. */ public function normalizePath(string $path): string { @@ -162,7 +162,7 @@ abstract class PlatformBase } /** - * 组合路径 + * Join path components. */ public function joinPath(string ...$parts): string { @@ -196,15 +196,15 @@ abstract class PlatformBase } /** - * 获取默认的 RPATH 路径列表(仅 macOS 需要) - * - * @param string|null $phpxDir phpx 目录路径 - * @param string|null $phpDir PHP 目录路径 - * @return array RPATH 路径数组 + * Get the default RPATH path list (only needed on macOS). + * + * @param string|null $phpxDir phpx directory path + * @param string|null $phpDir PHP directory path + * @return array RPATH path array */ public function getDefaultRpaths(?string $phpxDir = null, ?string $phpDir = null): array { - // 默认返回空数组,由子类重写 + // Return an empty array by default; subclasses may override. return []; } } diff --git a/src/Platform/UnixPlatform.php b/src/Platform/UnixPlatform.php index 9d7ef36b..2a1b4731 100644 --- a/src/Platform/UnixPlatform.php +++ b/src/Platform/UnixPlatform.php @@ -3,8 +3,8 @@ namespace TypePhp\Platform; /** - * Unix-like 平台基类(Linux, macOS) - * 包含 GCC/Clang 通用标志语法的共享实现 + * Base class for Unix-like platforms (Linux, macOS). + * Contains the shared implementation of common GCC/Clang flag syntax. */ abstract class UnixPlatform extends PlatformBase { @@ -107,9 +107,10 @@ abstract class UnixPlatform extends PlatformBase return $phpDir; } - // Ubuntu/PPA 多版本环境下 php8.4 与 php-config8.4 并存,而 - // php-config 可能被 update-alternatives 指向其它版本。优先依据 - // PHP_BINARY 的版本后缀定位版本化 php-config,避免 ABI 错配。 + // On Ubuntu/PPA multi-version installations, php8.4 and php-config8.4 coexist, + // while the unversioned php-config may be pointed at another version by + // update-alternatives. Prefer locating the versioned php-config from the + // version suffix of PHP_BINARY to avoid an ABI mismatch. $versionedConfig = $this->findVersionedPhpConfig(dirname(realpath(PHP_BINARY) ?: PHP_BINARY)); if ($versionedConfig !== null) { $prefix = $this->getPhpConfigValue($versionedConfig, '--prefix'); @@ -144,7 +145,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 获取 RPATH 选项 + * Get the RPATH options. */ public function getRpathOptions(array $paths): string { @@ -161,7 +162,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 获取 PIC 选项 + * Get the PIC option. */ public function getPicFlag(): string { @@ -169,7 +170,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 构建 PHP 包含路径(使用 php-config 动态获取) + * Build the PHP include paths (obtained dynamically via php-config). */ public function buildPhpIncludePaths(string $phpDir): array { @@ -209,7 +210,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 查找 php-config 可执行文件 + * Locate the php-config executable. */ protected function findPhpConfig(string $phpDir): ?string { @@ -273,14 +274,14 @@ abstract class UnixPlatform extends PlatformBase } } - // 依次返回第一个与当前 PHP 主次版本匹配的候选 + // Return the first candidate whose major/minor version matches the current PHP. foreach (array_unique($candidates) as $config) { if ($this->phpConfigMatchesCurrentPhp($config)) { return $config; } } - // 存在候选但版本均不匹配时给出明确错误 + // Report a clear error when candidates exist but none matches the version. if ($candidates !== []) { $this->reportPhpConfigVersionMismatch($candidates[0]); } @@ -296,7 +297,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 校验 php-config 的主次版本号是否与当前运行的 PHP 一致。 + * Verify that php-config's major/minor version matches the currently running PHP. */ private function phpConfigMatchesCurrentPhp(string $phpConfig): bool { @@ -346,7 +347,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 构建 PHP 库路径 + * Build the PHP library paths. */ public function buildPhpLibPaths(string $phpDir): array { @@ -355,7 +356,7 @@ abstract class UnixPlatform extends PlatformBase } /** - * 检测 PHP 库文件 + * Detect the PHP library files. */ public function detectPhpLibs(string $phpDir): array { diff --git a/src/Platform/Windows.php b/src/Platform/Windows.php index 3527c972..208c586c 100644 --- a/src/Platform/Windows.php +++ b/src/Platform/Windows.php @@ -3,22 +3,22 @@ namespace TypePhp\Platform; /** - * Windows 平台实现 + * Windows platform implementation. */ class Windows extends PlatformBase { /** - * PHP 库文件信息 + * PHP library file information. */ private array $phpLibs = []; /** - * 是否为 ZTS 模式 + * Whether this is a ZTS build. */ private bool $isZts = false; /** - * PHP SDK 路径 + * PHP SDK path. */ private string $phpSdkPath = ''; @@ -137,7 +137,7 @@ class Windows extends PlatformBase } /** - * 获取 PHP 库文件列表 + * Get the list of PHP library files. */ public function getPhpLibs(): array { @@ -145,7 +145,7 @@ class Windows extends PlatformBase } /** - * 判断是否为 ZTS 模式 + * Determine whether this is a ZTS build. */ public function isZts(): bool { @@ -153,7 +153,7 @@ class Windows extends PlatformBase } /** - * 获取 PHP SDK 路径 + * Get the PHP SDK path. */ public function getPhpSdkPath(): string { @@ -161,7 +161,7 @@ class Windows extends PlatformBase } /** - * 获取 Windows 子系统选项 + * Get the Windows subsystem options. */ public function getSubsystemOptions(bool $noConsole): string { @@ -173,7 +173,7 @@ class Windows extends PlatformBase } /** - * 获取 CRT 库配置 + * Get the CRT library configuration. */ public function getCrtConfig(): string { @@ -232,7 +232,7 @@ class Windows extends PlatformBase } /** - * 获取调试选项 + * Get the debug options. */ public function getDebugOptions(bool $debugInfo): string { @@ -254,7 +254,7 @@ class Windows extends PlatformBase } /** - * 构建 PHP SDK 包含路径 + * Build the PHP SDK include paths. */ public function buildPhpSdkIncludePaths(string $phpDir): array { @@ -265,7 +265,7 @@ class Windows extends PlatformBase $paths = [$phpSdkInclude]; - // 添加子目录 + // Add the subdirectories. $subDirs = ['main', 'Zend', 'TSRM', 'ext']; foreach ($subDirs as $subDir) { $subPath = $phpSdkInclude . '\\' . $subDir; @@ -278,18 +278,18 @@ class Windows extends PlatformBase } /** - * 构建 PHP SDK 库路径 + * Build the PHP SDK library paths. */ public function buildPhpSdkLibPaths(string $phpDir): array { $paths = []; - // 优先从 SDK/lib 读取 + // Prefer reading from SDK/lib. $phpLib = $phpDir . '\\SDK\\lib'; if (is_dir($phpLib)) { $paths[] = $phpLib; } else { - // 备选:尝试直接从 lib 目录 + // Fallback: try the lib directory directly. $phpLibAlt = $phpDir . '\\lib'; if (is_dir($phpLibAlt)) { $paths[] = $phpLibAlt; @@ -300,7 +300,7 @@ class Windows extends PlatformBase } /** - * 检测 PHP lib 文件并决定 ZTS/NTS 模式 + * Detect the PHP lib files and decide the ZTS/NTS mode. */ public function detectPhpLibs(string $phpDir): array { diff --git a/src/Preprocessor.php b/src/Preprocessor.php index f824e9a7..8c91e1ce 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -180,7 +180,7 @@ class Preprocessor extends CompilerBase $sorter = new StringSort(); $fileDeps = []; - // 构建依赖关系图 + // Build the dependency graph foreach ($this->symbolCallInFile as $file => $symbols) { $deps = []; foreach ($symbols as $symbol) { @@ -198,7 +198,7 @@ class Preprocessor extends CompilerBase $sortedFiles = $sorter->sort(); - // 添加未参与依赖管理的文件(非 stub 文件且不在已排序列表中) + // Append files that do not participate in dependency management (non-stub files not present in the sorted list) foreach ($list as $file) { if (!$this->isStubFile($file) and !in_array($file, $sortedFiles)) { $sortedFiles[] = $file; @@ -237,7 +237,7 @@ class Preprocessor extends CompilerBase $info = pathinfo($cppFile); $ext = $this->getPlatform()->getObjectExtension(); - // 保持与 cppFile 相同的路径分隔符 + // Keep the same path separator as cppFile $normalizedFile = str_replace('\\', '/', $cppFile); $normalizedMiscDir = str_replace('\\', '/', $this->getPhpxDir() . '/src/misc/'); if (str_starts_with($normalizedFile, $normalizedMiscDir)) { @@ -719,7 +719,7 @@ class Preprocessor extends CompilerBase foreach ($functionCalls as $call) { if ($call->name instanceof Node\Name) { - // 内置函数不参与依赖管理 + // Internal functions do not participate in dependency management $funcName = strtolower($call->name->toString()); if (!$this->isInternalFunction($funcName)) { $this->symbolCallInFile[$this->file][] = $funcName; @@ -741,7 +741,7 @@ class Preprocessor extends CompilerBase } } } - // 依赖去重 + // Deduplicate dependencies $this->symbolCallInFile[$this->file] = array_unique($this->symbolCallInFile[$this->file]); } @@ -846,7 +846,7 @@ class Preprocessor extends CompilerBase if ($this->stubFile && $this->stubImportLibrary === '' && !$param->type) { throw new \RuntimeException('No type for ' . $phpName); } - // 构造方法属性定义语法(Constructor Property Promotion) + // Constructor property promotion syntax if ($param->isPromoted()) { if (!$this->classDef or !$this->methodDef or $this->methodDef->name !== '__construct') { $this->fatalError($param, 'Promoted properties are not supported'); @@ -910,7 +910,7 @@ class Preprocessor extends CompilerBase $this->lowerArgumentDefault($param, $argInfo); } } elseif ($param->variadic) { - // 变长参数可以视为空数组默认值 + // A variadic parameter can be treated as an empty-array default value $argInfo->default = '{}'; $argInfo->defaultValue = new Node\Expr\Array_(); } @@ -960,7 +960,7 @@ class Preprocessor extends CompilerBase // Local stubs define C++ native functions and require an explicit ABI return type. // Generated external stubs may preserve an untyped PHP declaration as php::Var. if ($this->stubFile && $this->stubImportLibrary === '' && !$v->returnType) { - // 以下魔术方法都不能声明返回值类型 __construct()/__destruct()/__clone() + // The following magic methods must not declare a return type: __construct()/__destruct()/__clone() if (($this->method and !in_array($this->method, ['__construct', '__destruct', '__clone'])) or !$this->method) { $name = $this->class ? $this->class . '::' . $v->name : $v->name; $this->fatalError($v, 'The return type of the function `' . $name . '` must be specified'); @@ -997,7 +997,7 @@ class Preprocessor extends CompilerBase if ($nullableNativeReturn !== null) { [$returnType, $class] = $nullableNativeReturn; } - // 构造、析构、克隆方法不能有返回值 + // Constructor, destructor, and clone methods cannot have a return value if ($this->method and in_array($this->method, ['__construct', '__destruct', '__clone'])) { $returnType = Type::VOID; } @@ -1077,7 +1077,7 @@ class Preprocessor extends CompilerBase $this->fatalError($v, 'Zend-backed constructors cannot accept or return native objects'); } - // main 函数,返回值必须为 void 类型,参数必须为空或者 argc, argv 两个参数 + // The main function must return void and take either no parameters or the two parameters argc and argv if (!$this->class and !$this->namespace and $fnName === self::ENTRY_FUNCTION) { if (count($v->params) > 0) { if (count($v->params) != 2) { @@ -1158,7 +1158,7 @@ class Preprocessor extends CompilerBase } $this->fatalError($v, "Duplicate function `{$name}`"); } - // 禁止重定义内置函数 + // Forbid redefining built-in functions if (!$this->methodDef and $this->isInternalFunction($name)) { $this->fatalError($v, "The function `{$name}` is a built-in function and cannot be redefined"); } @@ -1241,7 +1241,7 @@ class Preprocessor extends CompilerBase $this->symbolCallInFile[$this->file][] = $parentClassLower; } $this->classDef->extends = $this->parentClass; - // 是否继承了内置类 + // Whether it inherits from an internal class $this->classDef->inheritedFromInternalClass = $this->isInternalClass($parentClassLower); } @@ -2146,11 +2146,11 @@ class Preprocessor extends CompilerBase $fullMethodNameLower = strtolower($fullMethodName); $fullClassNameLower = strtolower($fullClassName); - // 检查子类是否已覆盖此方法(子类先于父类被预处理的情况) + // Check whether a subclass already overrides this method (when the subclass is preprocessed before the parent) $isOverridden = $this->isMethodOverriddenInSubClasses($fullClassNameLower, $this->method); $this->classMethodOverride[$fullMethodNameLower] = $isOverridden; - // 查找父类是否有同名方法,递归向上标记父类方法已被覆盖 + // Find whether a parent class has a method with the same name, and recursively mark the parent method as overridden while (($parentClass = $this->symbols->parent($fullClassNameLower)) !== '') { $parentMethodLower = strtolower($parentClass . '::' . $this->method); if (isset($this->classMethodOverride[$parentMethodLower])) { @@ -2182,7 +2182,7 @@ class Preprocessor extends CompilerBase } /** - * 递归检查所有子类(及子类的子类)是否已定义了同名方法,用于处理子类先于父类被预处理的情况。 + * Recursively check whether any subclass (and its subclasses) has defined a method with the same name; handles the case where a subclass is preprocessed before its parent. */ private function isMethodOverriddenInSubClasses(string $classNameLower, string $method): bool { @@ -2400,7 +2400,7 @@ class Preprocessor extends CompilerBase // use THello1, THello2 { // hello as hello3; // } - // 未指定 trait,将添加所有 trait 的别名映射,在预处理阶段无法获取 trait 的方法列表 + // No trait specified: add alias mappings for all traits, since the trait's method list is unavailable during preprocessing $traits = $traitUse->traits; } else { $traits[] = $adaptation->trait; @@ -2409,9 +2409,9 @@ class Preprocessor extends CompilerBase $traitName = $this->getNamespacedClassName($this->parseIdentifier($trait)); $methodName = $adaptation->method->toString(); /* - * 例如: + * For example: * use TraitA { TraitA::method as newMethod} - * 这表示 TraitA::method() 会被重命名为 TraitA::newMethod() + * This means TraitA::method() is renamed to TraitA::newMethod() */ $aliases[$this->getFullMethodName($traitName, $methodName)][] = [ 'newName' => $adaptation->newName ? $adaptation->newName->toString() : $methodName, @@ -2425,9 +2425,9 @@ class Preprocessor extends CompilerBase } $methodName = $adaptation->method->toString(); /* - * 例如: + * For example: * use TraitA { TraitA::method insteadof TraitB} - * 这表示 TraitB::method() 将会被忽略,真正执行的是 TraitA::method() + * This means TraitB::method() is ignored, and TraitA::method() is actually executed */ foreach ($adaptation->insteadof as $trait2) { $traitName = $this->getNamespacedClassName($this->parseIdentifier($trait2)); diff --git a/src/PythonTools/Converter/PythonAstLoader.php b/src/PythonTools/Converter/PythonAstLoader.php index 6d10a2f7..155073e0 100644 --- a/src/PythonTools/Converter/PythonAstLoader.php +++ b/src/PythonTools/Converter/PythonAstLoader.php @@ -5,8 +5,8 @@ namespace TypePhp\PythonTools\Converter; use RuntimeException; /** - * 非 final:测试可子类化注入预制 AST 或模拟解析失败, - * 见 phpunit/src/PythonTools/PythonAstLoaderTest.php。 + * Intentionally non-final so tests can subclass it to inject a canned AST or simulate a parse failure; + * see phpunit/src/PythonTools/PythonAstLoaderTest.php. */ class PythonAstLoader { diff --git a/src/PythonTools/Converter/PythonToTypePhpConverter.php b/src/PythonTools/Converter/PythonToTypePhpConverter.php index 348215b2..829bf09a 100644 --- a/src/PythonTools/Converter/PythonToTypePhpConverter.php +++ b/src/PythonTools/Converter/PythonToTypePhpConverter.php @@ -15,7 +15,7 @@ final class PythonToTypePhpConverter /** @var array */ private array $definedFunctions = []; - /** @var array 被装饰的函数:调用点必须经变量间接调用装饰结果 */ + /** @var array Decorated functions: call sites must invoke the decorator result indirectly through a variable */ private array $decoratedFunctions = []; /** @var array */ @@ -53,12 +53,12 @@ final class PythonToTypePhpConverter foreach ($tree['body'] ?? [] as $node) { if (in_array($node['_type'] ?? '', ['Assign', 'AnnAssign', 'AugAssign'], true)) { - // 纯注解声明没有运行期值,不登记为模块全局变量 + // An annotation-only declaration has no runtime value, so it is not registered as a module global. $annotationOnly = ($node['_type'] ?? '') === 'AnnAssign' && ($node['value'] ?? null) === null; if (!$annotationOnly) { $targets = ($node['_type'] ?? '') === 'Assign' ? ($node['targets'] ?? []) : [$node['target'] ?? []]; foreach ($targets as $target) { - // 解构赋值展开为其中的名称元素 + // A destructuring assignment expands into its individual name elements. $elements = in_array($target['_type'] ?? '', ['Tuple', 'List'], true) ? ($target['elts'] ?? []) : [$target]; @@ -77,7 +77,7 @@ final class PythonToTypePhpConverter $name = (string) $node['name']; $this->definedFunctions[$name] = true; if (($node['decorator_list'] ?? []) !== []) { - // 装饰结果绑定到模块级变量,函数内调用需要 global 注入 + // The decorator result is bound to a module-level variable, so calls inside functions need a global injection. $this->decoratedFunctions[$name] = true; $this->moduleGlobals[$name] = true; } @@ -108,7 +108,7 @@ final class PythonToTypePhpConverter if ($this->moduleGlobals !== []) { $lines[] = $this->line('global ' . implode(', ', $this->variables(array_keys($this->moduleGlobals))) . ';'); } - // 装饰器重绑定先于其他顶层语句执行,使后续调用拿到装饰结果 + // Decorator rebinding runs before other top-level statements so that subsequent calls observe the decorated result. foreach ($functions as $function) { foreach ($this->decoratorRebindings($function) as $rebinding) { $lines[] = $this->line($rebinding); @@ -202,7 +202,7 @@ final class PythonToTypePhpConverter } /** - * Python 的 main 函数与 TypePHP 入口点冲突,重命名为 main_。 + * Python's main function conflicts with the TypePHP entry point, so it is renamed to main_. */ private function functionName(string $name): string { @@ -210,8 +210,8 @@ final class PythonToTypePhpConverter } /** - * 生成装饰器的重绑定语句(Python 自底向上应用装饰器)。 - * 装饰结果存入同名模块变量,调用点经变量间接调用。 + * Generate the rebinding statements for a function's decorators (Python applies decorators bottom-up). + * The decorated result is stored in a module variable of the same name, and call sites invoke it indirectly through that variable. * * @param array $function @return list */ @@ -233,7 +233,7 @@ final class PythonToTypePhpConverter /** @param array $node */ private function decoratorCallable(array $node): string { - // @dec(args):装饰器工厂,先求值再调用其返回值 + // @dec(args): a decorator factory; evaluate it first, then call its return value. if (($node['_type'] ?? '') === 'Call') { return $this->call($node); } @@ -312,7 +312,7 @@ final class PythonToTypePhpConverter $parts[] = match ($element['_type'] ?? '') { 'Name' => $this->variable((string) $element['id']), 'Attribute', 'Subscript' => $this->target($element), - // PHP 的 list 赋值不支持展开,嵌套元组的元素仍是 PyObject 无法直接解构 + // PHP list assignment does not support spreading, and nested tuple elements remain PyObject and cannot be destructured directly. 'Starred' => $this->unsupported($owner, 'starred destructuring is not supported'), default => $this->unsupported($owner, 'nested destructuring is not supported'), }; @@ -337,7 +337,7 @@ final class PythonToTypePhpConverter { $target = $this->target($node['target']); $operator = $node['op']['_type'] ?? ''; - // PHP 没有 //= 与 @=,展开为对应的运算符函数调用 + // PHP has no //= or @= operators, so these expand into the corresponding operator function calls. if ($operator === 'FloorDiv' || $operator === 'MatMult') { $function = $operator === 'FloorDiv' ? 'python\\operator\\floordiv' : 'python\\operator\\matmul'; return [$this->line($target . ' = ' . $function . '(' . $target . ', ' . $this->expression($node['value']) . ');')]; @@ -530,7 +530,7 @@ final class PythonToTypePhpConverter { $targets = []; $walk = function (array $target) use (&$walk, &$targets, $node): void { - // del (a, b) / del [a, b] 逐项展开 + // del (a, b) / del [a, b] expands element by element. if (in_array($target['_type'] ?? '', ['Tuple', 'List'], true)) { foreach ($target['elts'] ?? [] as $element) { $walk($element); @@ -585,7 +585,7 @@ final class PythonToTypePhpConverter if (($function['_type'] ?? '') === 'Name') { $name = (string) $function['id']; if (isset($this->decoratedFunctions[$name])) { - // 装饰结果绑定在同名变量上,必须经变量间接调用 + // The decorator result is bound to a variable of the same name, so it must be invoked indirectly through that variable. $callable = $this->variable($name); } elseif (isset($this->importedSymbols[$name])) { $symbol = $this->importedSymbols[$name]; diff --git a/src/Resolver/MagicMethodDetector.php b/src/Resolver/MagicMethodDetector.php index f4644a6d..9da0ad6b 100644 --- a/src/Resolver/MagicMethodDetector.php +++ b/src/Resolver/MagicMethodDetector.php @@ -201,7 +201,7 @@ trait MagicMethodDetector } } - // 重建 params 字符串,使 C++ 函数签名使用 auto-fill 后的类型 + // Rebuild the params string so the C++ function signature uses the auto-filled types $list = []; foreach ($fnDef->argInfoList as $argInfo) { if ($argInfo->variadic) { diff --git a/src/Resolver/NameResolutionTrait.php b/src/Resolver/NameResolutionTrait.php index 4c21e5a4..177295dd 100644 --- a/src/Resolver/NameResolutionTrait.php +++ b/src/Resolver/NameResolutionTrait.php @@ -69,12 +69,12 @@ trait NameResolutionTrait } /** - * 将 trait 方法参数中的类名 Name 节点升级为 Name\FullyQualified。 - * 对于已由 parseTypeDecl() 解析的限定名(含 \),直接升级节点类型; - * 对于尚未解析的非限定名(如 NullableType 内层,parseTypeDecl 返回 TYPE_VAR 跳过了解析), - * 先通过 useAliases/useNamespaces 解析再升级。 - * gen_stub.php 的 SimpleType::fromNode() 依赖 isFullyQualified() 判断是否需要再次解析, - * 若不升级为 FullyQualified,在上下文丢失后会被错误地追加当前 namespace 前缀。 + * Upgrade the class-name Name node in a trait method parameter to Name\FullyQualified. + * For qualified names (containing \) already resolved by parseTypeDecl(), upgrade the node type directly; + * for unresolved unqualified names (such as the inner type of a NullableType, which parseTypeDecl skips by returning TYPE_VAR), + * resolve them via useAliases/useNamespaces first and then upgrade. + * gen_stub.php's SimpleType::fromNode() relies on isFullyQualified() to decide whether to re-resolve; + * if the name is not upgraded to FullyQualified, the current namespace prefix is wrongly appended once the context is lost. */ protected function upgradeToFullyQualifiedName(?NodeAbstract $type): ?NodeAbstract { @@ -130,7 +130,7 @@ trait NameResolutionTrait } /** - * 函数名称处理,补齐 namespace + * Process the function name and prepend the namespace when required. */ public function getNamespacedFuncName(string $funcName): string { @@ -144,7 +144,7 @@ trait NameResolutionTrait } /** - * @param string $class 一定是带有命名空间的完整类名 + * @param string $class must be a fully qualified class name including the namespace */ protected function resolveTypeDecl(?NodeAbstract $type, int $what): array { @@ -155,17 +155,17 @@ trait NameResolutionTrait protected function parseTypeDecl(?NodeAbstract $type, int $what, string &$class): string { - // 未定义类型视为 var (mixed, any) + // An undefined type is treated as var (mixed, any) if ($type === null) { return Type::VAR; } if ($type instanceof UnionType || $type instanceof NullableType || $type instanceof IntersectionType) { - // 复杂类型静态阶段统一按 mixed/var 处理,运行时再由 typeCheck 兜底。 + // Complex types are uniformly treated as mixed/var at the static stage; the runtime typeCheck provides the fallback. return Type::VAR; } else { $typeName = $this->parseIdentifier($type); $typeNameLower = strtolower($typeName); - // 属性和类常量的类型不能声明为 void/never ,只有返回值可以 + // Property and class-constant types cannot be declared void/never; only return types can if ($what !== self::DECL_TYPE_OF_RETURN and ($typeNameLower === 'void' or $typeNameLower === 'never')) { $this->fatalError($type, 'The type `void`/`never` is allowed only for return type'); } elseif (isset($this->zendTypeMap[$typeNameLower])) { @@ -179,12 +179,12 @@ trait NameResolutionTrait } $class = $this->classDef->extends; } elseif ($typeName === 'static') { - // static 类无法在编译期获取 + // The static class cannot be determined at compile time $class = ''; } else { $class = $this->getNamespacedClassName($typeName); } - // Trait 在注入 class 需要使用完整类名 + // When a trait is injected into a class, the fully qualified class name is required if ($class and $this->classDef and $this->classDef->trait) { $type->name = $class; } diff --git a/src/Resolver/PropertyAccessResolver.php b/src/Resolver/PropertyAccessResolver.php index e53858b1..18a5266b 100644 --- a/src/Resolver/PropertyAccessResolver.php +++ b/src/Resolver/PropertyAccessResolver.php @@ -70,7 +70,7 @@ final class PropertyAccessResolver while (true) { $classDef = $this->compiler->getClassDef($findClass); if ($classDef === null) { - // 非编译单元内的类:尝试按内置类的声明属性解析(offset 缓存) + // Class outside the compilation unit: attempt to resolve it by the internal class's declared property (offset cache) return $this->resolveInternalClassProperty($expr, $property, $findClass, $class, $scope, $static); } @@ -160,11 +160,11 @@ final class PropertyAccessResolver } /** - * 解析 PHP 内置类的声明属性,使其可以进入稳定属性 offset 缓存。 + * Resolve the declared property of a PHP internal class so it can enter the stable property offset cache. * - * 仅处理反射可见的声明属性:动态属性、魔术属性(__get/__set)反射不可见, - * 返回 null 回退到按名字符串查找路径。内置类在 MINIT 注册、进程级存活, - * 其声明属性的 offset 终身不变,缓存安全。 + * Only declared properties visible to reflection are handled: dynamic properties and magic properties (__get/__set) are not visible to reflection, + * so return null to fall back to the by-name string lookup path. Internal classes are registered at MINIT and live for the whole process, + * so the offset of their declared properties never changes and caching is safe. */ private function resolveInternalClassProperty( NodeAbstract $expr, @@ -182,7 +182,7 @@ final class PropertyAccessResolver return null; } $propRef = $ref->getProperty($property); - // PHP 8.4 属性钩子必须由引擎调用,offset 直读会绕过钩子,回退字符串路径 + // PHP 8.4 property hooks must be invoked by the engine; reading the offset directly would bypass the hook, so fall back to the string path if ($propRef->hasHooks()) { return null; } @@ -209,8 +209,8 @@ final class PropertyAccessResolver $this->fatal($expr, "Cannot access private property `{$property}` of class `{$displayClass}`"); } - // 复合类型(union/intersection)的运行时检查结构依赖 AST 构建, - // 无法从反射便捷还原,回退字符串路径以保证类型安全 + // The runtime check structure for composite types (union/intersection) depends on the AST, + // which cannot be conveniently reconstructed from reflection, so fall back to the string path to preserve type safety $propType = $propRef->getType(); if ($propType !== null && !$propType instanceof \ReflectionNamedType) { return null; diff --git a/src/Resolver/Reflection.php b/src/Resolver/Reflection.php index 1eb2ff3f..55da4cc8 100644 --- a/src/Resolver/Reflection.php +++ b/src/Resolver/Reflection.php @@ -188,8 +188,8 @@ class Reflection } /** - * 当参数索引超出声明范围时,尝试将最后一个参数作为变长参数(...$rest)获取。 - * 返回变长参数对象或 null。 + * When the parameter index exceeds the declared range, try to obtain the last parameter as a variadic parameter (...$rest). + * Returns the variadic parameter object, or null. */ public static function getVariadicParameter(string $funcName, string $className = ''): ?\ReflectionParameter { diff --git a/src/Symbol/SymbolRepository.php b/src/Symbol/SymbolRepository.php index d86c5d38..ee461ae6 100644 --- a/src/Symbol/SymbolRepository.php +++ b/src/Symbol/SymbolRepository.php @@ -9,12 +9,13 @@ use TypePhp\Entity\InterfaceDef; final class SymbolRepository { /** - * 存储所有函数、类方法的定义,key 是 native name,命名空间需要转为 `_`,并且必须为小写 + * Stores the definitions of all functions and class methods. The key is the native name: + * the namespace must be converted to `_` and the result must be lowercase. * @var array */ private array $functions = []; /** - * key 类名,包含命名空间 + * Keyed by class name, including the namespace. * @var array */ private array $classes = []; diff --git a/src/Translator.php b/src/Translator.php index dac9b883..57fb2b8a 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -80,7 +80,7 @@ class Translator extends Preprocessor protected array $argInfoHeaderFiles = []; protected array $registerSymbols = []; - // Windows 资源文件配置(图标、版本信息等) + // Windows resource file configuration (icon, version info, etc.) protected array $resourceConfig = []; protected array $globalHeaders = [ 'cstring', @@ -112,8 +112,9 @@ class Translator extends Preprocessor $this->preprocessArgvAdvanced(); $this->climate->arguments->parse(); - // 只读取命令行参数,不立即应用(等待 YAML 解析后再应用) - // 这样可以确保优先级:命令行 > YAML > 默认值 + // Only read the command-line arguments here; do not apply them yet + // (they are applied after YAML parsing). This preserves the priority: + // command line > YAML > defaults. $this->internalFunctions = []; foreach (get_defined_functions()['internal'] as $functionName) { $function = Reflection::getFunction($functionName); @@ -133,13 +134,13 @@ class Translator extends Preprocessor exit(0); } - // 提前处理 --no-color,确保后续所有输出均为无颜色模式 + // Handle --no-color early so all subsequent output is colorless. if ($this->climate->arguments->defined('no-color')) { $this->climate->forceAnsiOff(); } - // 检测操作系统、编译器以及 Windows 平台的 PHP lib 文件 + // Detect the OS, the compiler, and (on Windows) the PHP lib files. $this->detectPlatform(); } @@ -152,7 +153,8 @@ class Translator extends Preprocessor $constants = []; foreach ($groups as $groupName => $group) { - // 编译器进程中的用户常量属于被编译程序的运行时状态,不能在静态阶段展开。 + // User constants in the compiler process belong to the compiled + // program's runtime state and must not be expanded in the static phase. if (strcasecmp((string) $groupName, 'user') === 0 || Reflection::isTypePhpExtension($groupName) || !is_array($group)) { @@ -166,7 +168,7 @@ class Translator extends Preprocessor } /** - * 检测操作系统、编译器以及 Windows 平台的 PHP lib 文件 + * Detect the OS, the compiler, and (on Windows) the PHP lib files. */ protected function detectPlatform(): void { @@ -295,43 +297,45 @@ class Translator extends Preprocessor } /** - * 应用命令行参数(在 YAML 解析后调用,确保命令行参数优先级最高) + * Apply command-line arguments (called after YAML parsing so command-line + * arguments take the highest priority). */ protected function applyCommandLineArguments(): void { $this->applyPhpVersionCommandLineArgument(); - // 优化级别 + // Optimization level if ($this->climate->arguments->defined('optimize')) { $this->optimizeLevel = $this->climate->arguments->get('optimize'); } - // 构建模式 + // Build mode if ($this->climate->arguments->defined('mode')) { $this->setBuildMode($this->climate->arguments->get('mode')); } - // 调试行号 + // Debug line number if ($this->climate->arguments->defined('debug-line')) { $this->debugLine = intval($this->climate->arguments->get('debug-line')); } - // 最大并行任务数 + // Maximum number of parallel jobs if ($this->climate->arguments->defined('job')) { $this->maxJob = intval($this->climate->arguments->get('job')); } - // 调试模式 + // Debug mode if ($this->climate->arguments->defined('debug')) { $this->debug = true; } - // 禁用字面量字符串优化 + // Disable literal string optimization if ($this->climate->arguments->defined('no-literal-strings')) { $this->noLiteralStrings = true; } - // 启用性能分析(需强制重编译 misc 文件以确保 PPROF_ON 宏生效,仅 Linux 支持) + // Enable profiling (forces recompilation of misc files so the PPROF_ON + // macro takes effect; Linux only) if ($this->climate->arguments->defined('profile')) { if (!$this->isLinux()) { $this->climate->error('--profile is only supported on Linux (requires gperftools)'); @@ -340,12 +344,12 @@ class Translator extends Preprocessor $this->enableProfiler = true; } - // 禁用进度条 + // Disable the progress bar if ($this->climate->arguments->defined('no-progress')) { $this->noProgress = true; } - // 隐藏控制台窗口 + // Hide the console window if ($this->climate->arguments->defined('no-console')) { $this->noConsole = true; } @@ -355,27 +359,27 @@ class Translator extends Preprocessor $this->sanitize = $this->climate->arguments->get('sanitize'); } - // C++ 标准版本 + // C++ standard version if ($this->climate->arguments->defined('cxx-std')) { $this->cxxStd = $this->climate->arguments->get('cxx-std'); } - // 目标 CPU 指令集 + // Target CPU instruction set if ($this->climate->arguments->defined('march')) { $this->march = $this->climate->arguments->get('march'); } - // 交叉编译目标平台 + // Cross-compilation target platform if ($this->climate->arguments->defined('target-platform')) { $this->targetPlatform = $this->climate->arguments->get('target-platform'); } - // 输出文件名/路径 + // Output file name/path if ($this->climate->arguments->defined('output')) { $this->setOutputPath($this->climate->arguments->get('output')); } - // 构建目录 + // Build directory if ($this->climate->arguments->defined('build-dir')) { $buildDir = $this->climate->arguments->get('build-dir'); if (!empty($buildDir)) { @@ -383,35 +387,39 @@ class Translator extends Preprocessor } } - // 干运行模式 + // Dry-run mode if ($this->climate->arguments->defined('dry')) { $this->dryRun = true; } - // 用户自定义 C++ include 路径(直接从 argv 解析以支持多值) + // User-defined C++ include paths (parsed directly from argv to support + // multiple values) if ($this->hasRepeatableArgvFlag(['-I', '--include-path'])) { $this->userIncludePaths = $this->parseRepeatableArgv(['-I', '--include-path']); } - // 用户自定义预处理器宏(直接从 argv 解析以支持多值) + // User-defined preprocessor macros (parsed directly from argv to support + // multiple values) if ($this->hasRepeatableArgvFlag(['-D', '--define'])) { $this->userDefines = $this->parseRepeatableArgv(['-D', '--define']); } - // 链接时优化 + // Link-time optimization if ($this->climate->arguments->defined('lto')) { $this->enableLto = true; } - // clang-format 代码格式化(默认关闭,需显式 --format 开启) + // clang-format code formatting (disabled by default; requires explicit --format) if ($this->climate->arguments->defined('format')) { $this->enableCodeFormattingIfAvailable('--format'); } - // 用户自定义链接库(直接从 argv 解析以支持多值) + // User-defined link libraries (parsed directly from argv to support + // multiple values) if ($this->hasRepeatableArgvFlag(['-l', '--link-lib'])) { $this->linkLibs = $this->parseRepeatableArgv(['-l', '--link-lib']); } - // 用户自定义库搜索路径(直接从 argv 解析以支持多值) + // User-defined library search paths (parsed directly from argv to support + // multiple values) if ($this->hasRepeatableArgvFlag(['-L', '--link-path'])) { $this->linkPaths = $this->parseRepeatableArgv(['-L', '--link-path']); } @@ -426,25 +434,26 @@ class Translator extends Preprocessor } /** - * 从原始 $argv 中解析可重复参数,支持 -X val 和 --long val 两种形式。 - * CLImate 的 multiple 选项只能保留最后一个值,因此需要手动解析。 + * Parse repeatable arguments from the raw $argv, supporting both the + * "-X val" and "--long val" forms. CLImate's "multiple" option only keeps + * the last value, so these must be parsed manually. * - * @param string[] $flags 要匹配的标志列表,如 ['-I', '--include-path'] - * @return string[] 收集到的所有值 + * @param string[] $flags Flags to match, e.g. ['-I', '--include-path'] + * @return string[] All collected values */ protected function parseRepeatableArgv(array $flags): array { global $argv; $values = []; for ($i = 1; $i < count($argv); $i++) { - // 精确匹配标志(如 -I, --include-path) + // Exact flag match (e.g. -I, --include-path) if (in_array($argv[$i], $flags, true) && isset($argv[$i + 1]) && $argv[$i + 1] !== '' && $argv[$i + 1][0] !== '-') { $values[] = $argv[$i + 1]; - $i++; // 跳过值 + $i++; // Skip the value } - // 合并形式:-I/path 或 --include-path=/path + // Combined form: -I/path or --include-path=/path elseif (!$this->isLongFlagWithEquals($argv[$i], $flags, $values)) { - // 检查短标志合并:-I/path + // Check short-flag combined form: -I/path foreach ($flags as $flag) { if (strlen($flag) === 2 && $flag[0] === '-') { $short = substr($flag, 1); @@ -485,7 +494,7 @@ class Translator extends Preprocessor } /** - * 处理 --flag=value 格式的长标志 + * Handle long flags in the --flag=value form. */ private function isLongFlagWithEquals(string $arg, array $flags, array &$values): bool { @@ -551,7 +560,8 @@ class Translator extends Preprocessor } else { $this->save($cppCode, $cppFile); } - // 生成 stub 文件,依赖 convert 阶段的 use 等信息 + // Generate the stub file, which depends on the use statements + // and other info collected during the convert phase. $this->genStubFile($this->file); return $cppCode === '' ? null : $cppFile; } catch (Redo $e) { @@ -583,8 +593,8 @@ class Translator extends Preprocessor } /** - * 初始化新的 Platform 和 Backend 抽象层 - * 这是一个渐进式迁移,保持向后兼容 + * Initialize the new Platform and Backend abstraction layers. + * This is an incremental migration that preserves backward compatibility. */ protected function initializeNewArchitecture(): void { @@ -592,7 +602,7 @@ class Translator extends Preprocessor $platform = $this->platform ?? PlatformFactory::create(); $this->platform = $platform; - // 自动检测平台和编译器 + // Auto-detect the platform and compiler $result = CompilerFactory::autoDetect($this->cppCompiler, $platform); $this->platform = $result['platform']; $this->compilerBackend = $result['compiler']; @@ -601,7 +611,7 @@ class Translator extends Preprocessor "Initialized new architecture: {$this->platform->getName()} + {$this->compilerBackend->getName()}" ); } catch (\Exception $e) { - // 如果初始化失败,回退到旧逻辑 + // Fall back to the legacy logic if initialization fails $this->climate->warning( "Failed to initialize new architecture: {$e->getMessage()}. Using legacy mode." ); @@ -611,14 +621,14 @@ class Translator extends Preprocessor } /** - * 设置 C++ 编译器(从配置文件读取) + * Set the C++ compiler (read from the config file). */ public function setCppCompiler(string $compiler): void { $this->cppCompiler = $compiler; $this->climate->info("Using compiler from config: {$this->cppCompiler}"); - // 重新初始化 Backend + // Re-initialize the Backend $this->initializeNewArchitecture(); } @@ -641,7 +651,8 @@ class Translator extends Preprocessor public function setTargetName(string $name): void { - // 如果指定了路径(包含目录分隔符),提取目录和文件名 + // If a path was given (contains a directory separator), split it into + // directory and file name. if (str_contains($name, '/') || str_contains($name, '\\')) { $this->outputDir = dirname($name); $name = basename($name); @@ -874,7 +885,7 @@ class Translator extends Preprocessor } $code .= "// class entry \n"; - // 确保数组大小至少为 1,避免 C/C++ 编译错误 + // Ensure the array has at least one element to avoid C/C++ compile errors. $code .= 'static THREAD_LOCAL zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . max(1, count($this->classMap)) . '];' . PHP_EOL; // Internal/compiled symbols have module lifetime. They are initialized // lazily after PHP startup, so disable_functions/disable_classes have @@ -888,7 +899,8 @@ class Translator extends Preprocessor $code .= $this->genPythonModuleStorage(); $code .= "// property \n"; - // 无动态 propMap:属性 offset 缓存仅覆盖编译类/内置类的声明属性(见 getPropertyId) + // No dynamic propMap: the property offset cache only covers declared + // properties of compiled/built-in classes (see getPropertyId). $code .= 'static php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . max(1, count($this->persistentPropMap)) . ']{};' . PHP_EOL; $code .= "// functions \n"; @@ -1331,8 +1343,10 @@ CODE; } /** - * 检查 phpx/src/misc/ 下的源文件是否已有有效缓存,始终生效(除非指定 --force)。 - * 缓存必须匹配编译命令和 PHP ABI,且 .o 文件必须不早于源文件和 phpx 头文件。 + * Check whether source files under phpx/src/misc/ have a valid cache, always + * effective (unless --force is specified). The cache must match the compile + * command and the PHP ABI, and the .o file must not be older than the source + * files and phpx headers. */ public function hasMiscObjectFileCache(string $cppFile): bool { @@ -1427,7 +1441,7 @@ CODE; } /** - * 判断文件是否为 C++ 源文件 + * Determine whether a file is a C++ source file. */ protected function isCppFile(string $filePath): bool { @@ -1436,10 +1450,10 @@ CODE; } /** - * 根据文件扩展名获取语言类型标识(用于 -x 参数). + * Get the language type identifier from the file extension (used for the -x flag). * - * @return string|null 语言标识(c, assembler, objective-c, objective-c++), - * 或 null 表示使用默认检测(C++ 文件) + * @return string|null Language identifier (c, assembler, objective-c, objective-c++), + * or null to use the default detection (C++ files). */ protected function getLanguageFromExtension(string $filePath): ?string { @@ -1455,7 +1469,7 @@ CODE; } /** - * 判断文件是否为原生编译型源文件(C/C++/汇编/ObjC 等). + * Determine whether a file is a natively compiled source file (C/C++/asm/ObjC, etc.). */ protected function isNativeSourceFile(string $filePath): bool { @@ -1521,7 +1535,7 @@ CODE; { $job = $this->maxJob; - // embed 需要 main 函数,以及 cli 的内置函数定义 + // The embed build needs the main function and the CLI's built-in function definitions. if ($this->isBuildModeEmbed()) { $runtimeSource = $this->getPhpxDir() . '/src/misc/typephp_runtime.cc'; // PHPX 2.6.3 keeps the common runtime in typephp_main.cc. Newer @@ -1540,14 +1554,14 @@ CODE; $this->preparePhpXPrecompiledHeader(); - // Windows 平台:编译资源文件(图标、版本信息等) + // Windows: compile the resource file (icon, version info, etc.) $this->compileResourceFile(); if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) { return $this->compileSourceFile($sourceFiles); } - // Unix/Linux/macOS 使用 pcntl 并行编译 + // Unix/Linux/macOS compile in parallel using pcntl return $this->compileWithPcntl($sourceFiles, $job); } @@ -1637,7 +1651,7 @@ CODE; } /** - * Unix/Linux/macOS 平台并行编译(使用 pcntl) + * Parallel compilation on Unix/Linux/macOS (using pcntl). */ protected function pcntlWait(?int &$status): int { @@ -1755,7 +1769,7 @@ CODE; { $targetFile = $this->getTargetFileName(); - // Windows 平台:将 .res 资源文件加入链接 + // Windows: add the .res resource file to the link if ($this->isWindows() && $this->hasResourceFile()) { $resFile = $this->getResourceResFile(); if (file_exists($resFile)) { @@ -2558,7 +2572,7 @@ CODE; $this->sanitize = (string) $sanitize; } - // 读取 cxx-flags + // Read cxx-flags $cxxFlags = $cfg['cxx-flags'] ?? null; if (!empty($cxxFlags)) { if (is_array($cxxFlags)) { @@ -2568,25 +2582,25 @@ CODE; } } - // 读取 cxx-std + // Read cxx-std $cxxStd = $cfg['cxx-std'] ?? null; if (!empty($cxxStd)) { $this->cxxStd = $cxxStd; } - // 读取 march(目标 CPU 指令集) + // Read march (target CPU instruction set) $march = $cfg['march'] ?? null; if (!empty($march)) { $this->march = $march; } - // 读取 target-platform + // Read target-platform $targetPlatform = $cfg['target-platform'] ?? null; if (!empty($targetPlatform)) { $this->targetPlatform = (string) $targetPlatform; } - // 读取 build-dir + // Read build-dir $buildDir = $cfg['build-dir'] ?? null; if (!empty($buildDir)) { $this->setBuildDir($this->resolvePath((string) $buildDir, $projectDir, 'Build path')); @@ -2596,7 +2610,7 @@ CODE; $this->dryRun = true; } - // 读取 ld-flags + // Read ld-flags $ldflags = $cfg['ld-flags'] ?? null; if (!empty($ldflags)) { if (is_array($ldflags)) { @@ -2606,7 +2620,7 @@ CODE; } } - // 读取 include-paths + // Read include-paths $includePaths = $cfg['include-paths'] ?? null; if (!empty($includePaths) && is_array($includePaths)) { foreach ($includePaths as $includePath) { @@ -2614,7 +2628,7 @@ CODE; } } - // 读取 defines + // Read defines $defines = $cfg['defines'] ?? null; if (!empty($defines) && is_array($defines)) { foreach ($defines as $define) { @@ -2622,17 +2636,17 @@ CODE; } } - // 读取 lto + // Read lto if (!empty($cfg['lto'])) { $this->enableLto = true; } - // 读取 format + // Read format if (!empty($cfg['format'])) { $this->enableCodeFormattingIfAvailable('YAML format'); } - // 读取 link-libs + // Read link-libs $linkLibs = $cfg['link-libs'] ?? null; if (!empty($linkLibs) && is_array($linkLibs)) { foreach ($linkLibs as $lib) { @@ -2640,7 +2654,7 @@ CODE; } } - // 读取 link-paths + // Read link-paths $linkPaths = $cfg['link-paths'] ?? null; if (!empty($linkPaths) && is_array($linkPaths)) { foreach ($linkPaths as $linkPath) { @@ -2675,7 +2689,8 @@ CODE; } } - // 读取 output/name。name 只表示目标名,不能按 YAML 目录解析成输出路径。 + // Read output/name. `name` only denotes the target name; it must not be + // resolved against the YAML directory as an output path. $output = $cfg['output'] ?? null; if (!empty($output)) { $this->setOutputPath($this->resolvePath((string) $output, $projectDir, 'Output path')); @@ -2683,19 +2698,19 @@ CODE; $this->setTargetName((string) $cfg['name']); } - // 读取 cpp-compiler + // Read cpp-compiler $cppCompiler = $cfg['cpp-compiler'] ?? null; if (!empty($cppCompiler)) { $this->setCppCompiler($cppCompiler); } - // 读取 mode/type/build-mode(支持 CLI/YAML 两套命名) + // Read mode/type/build-mode (supports both the CLI and YAML naming) $buildMode = $cfg['mode'] ?? $cfg['build-mode'] ?? $cfg['type'] ?? null; if (!empty($buildMode)) { $this->setBuildMode((string) $buildMode); } - // 读取 ignore(支持中横线和下划线) + // Read ignore (supports both hyphen and underscore) $ignore = $cfg['ignore'] ?? null; if (!empty($ignore)) { if (!is_array($ignore)) { @@ -2713,13 +2728,13 @@ CODE; } } - // 读取 resource(Windows 资源配置:图标、版本信息) + // Read resource (Windows resource config: icon, version info) $resource = $cfg['resource'] ?? null; if (!empty($resource)) { if (!is_array($resource)) { $this->error('`resource` must be array'); } - // 验证图标文件是否存在 + // Verify that the icon file exists if (!empty($resource['icon'])) { $iconPath = $resource['icon']; if (!preg_match('/^[A-Za-z]:\\|^\//', $iconPath)) { @@ -2733,7 +2748,7 @@ CODE; $this->resourceConfig['_projectDir'] = $projectDir; } - // 读取 manifest(Windows 清单文件,与 resource 同级,缺省不携带) + // Read manifest (Windows manifest file, same level as resource, omitted by default) $manifest = $cfg['manifest'] ?? null; if (!empty($manifest)) { if (!is_string($manifest)) { @@ -2893,7 +2908,7 @@ CODE; foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parent) { $tmpCe = self::PREFIX . 'class_entry_' . $this->escapeCeName($parent); - // 不存在的接口,说明可能是内置接口 + // A non-existent interface is likely a built-in interface if (!$this->hasInterface($parent)) { $sorter->add($tmpCe); } @@ -2917,7 +2932,7 @@ CODE; $deps = []; $parent = $classDef->extends; if ($parent) { - // 不存在的父类,说明可能是内置类 + // A non-existent parent is likely a built-in class $tmpCe = $this->getParentClassCe($classDef); if (!$this->hasClass($parent)) { $sorter->add($tmpCe); @@ -3567,8 +3582,9 @@ CODE; ); } - // 如果不是继承自内置类,需要检查父类是否存在,在预处理阶段只需检查了是否继承内置类 - // 目前不允许继承自动态加载的自定义类 + // When not inheriting from a built-in class, verify the parent exists. + // The preprocess phase only checks whether a built-in class is inherited. + // Currently, inheriting from an autoloaded custom class is not allowed. if ($this->classDef->extends and !$this->classDef->inheritedFromInternalClass) { $parentClass = $this->getNamespacedClassName($this->parseIdentifier($class->extends)); if ($this->hasClass($parentClass)) { @@ -3579,7 +3595,7 @@ CODE; 'Native and ZendVM-backed classes cannot inherit from each other' ); } - // 父类是 final 无法继承 + // The parent class is final and cannot be extended if ($parent->flags & Modifiers::FINAL) { $this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$parentClass}`"); } @@ -3787,7 +3803,9 @@ CODE; $isEntryFunction = $this->hasFunction(self::ENTRY_FUNCTION) && $functionDef === $this->getFunction(self::ENTRY_FUNCTION); if ($this->isBuildModeBin() && $isEntryFunction) { - // $_SERVER 的初始化必须置于 main 入口函数内,以确保在其被访问前,运行环境及超全局上下文已完全就绪。 + // $_SERVER initialization must live inside the main entry function so + // the runtime environment and superglobal context are fully ready + // before it is accessed. $cppCode .= $this->registerServerEnvironment($functionDef->sourceFile); } $callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : ''; @@ -3817,8 +3835,10 @@ CODE; private function registerServerEnvironment(string $entryFile): string { /** - * 对于常驻内存型应用,执行完当前逻辑后,会立即进入长时间的事件循环等待。 - * 因此,这些变量仅作为临时用途,用完后应即刻销毁,无需长期持有。 + * For long-running (resident) applications, control enters a long-lived + * event loop immediately after the current logic finishes. These + * variables are therefore only temporary and should be destroyed as soon + * as they are used, rather than held for the long term. */ $indent = $this->getIndent(); $cppCode = $indent . "const char *value = " . $this->genCharPtr($entryFile, true) . ';' . PHP_EOL; @@ -3870,7 +3890,7 @@ CODE; { $cppCode = ''; - // 接口没有方法实体 + // Interfaces have no method bodies if ($classDef instanceof ClassDef && $classDef->trait === null) { if ($classDef->nativeObject) { return ''; @@ -3927,14 +3947,15 @@ CODE; } $this->functionDef = $this->getFunction($name); - // 类方法不要保存到 functions 中 + // Class methods are not stored in `functions` if ($this->methodDef) { $this->methodDef->functionDef = $this->functionDef; } else { $this->functionDefineInFile[$name] = $this->functionDef; } - // stub 函数,没有函数的具体实现,只有声明,实现在 C++ 或者 .so 中定义 + // Stub functions have no concrete implementation, only a declaration; + // the implementation is defined in C++ or a .so file. if ($this->functionDef->stub) { $this->resetFunction(); return ''; @@ -4062,7 +4083,7 @@ CODE; $code .= $preamble . PHP_EOL; } $this->indentLevel--; - // 构建 PHP 级别的函数名用于 debug backtrace + // Build the PHP-level function name for debug backtraces if ($this->class) { $debugName = $this->class . '::' . $this->function; } else { @@ -4103,7 +4124,8 @@ CODE; } /** - * 检查父类方法是否可以被重写,私有方法不能被重写,方法签名必须兼容 + * Check whether a parent method can be overridden: private methods cannot be + * overridden, and the signature must be compatible. */ protected function checkParentMethodCanBeOverridden(Node\Stmt\ClassMethod $v, string $name): void { @@ -4118,7 +4140,7 @@ CODE; if (!$extends) { break; } - // 父类是内置类 + // The parent class is a built-in class if ($classDef->inheritedFromInternalClass) { $modifiers = Reflection::getClassMethodModifiers($extends, $name); if ($modifiers & \ReflectionMethod::IS_PRIVATE) { @@ -5066,7 +5088,8 @@ CODE; && !$this->hasMatchingOverrideDeclaration($this->classDef, $name)) { $this->fatalMissingOverride($v, $this->classDef->getNamespacedName(false), $name); } - // 预处理阶段没有父类的信息,只能在实现阶段检查 + // The preprocess phase has no parent-class info, so the check can + // only run in the implementation phase. $this->checkParentMethodCanBeOverridden($v, $name); $methodCodes[$name] = $this->parseFunction($v); } diff --git a/src/TypeSystem/NativeTypeCompatibilityTrait.php b/src/TypeSystem/NativeTypeCompatibilityTrait.php index e223fef9..bc25bcc9 100644 --- a/src/TypeSystem/NativeTypeCompatibilityTrait.php +++ b/src/TypeSystem/NativeTypeCompatibilityTrait.php @@ -30,10 +30,14 @@ trait NativeTypeCompatibilityTrait protected function isInheritedFrom(string $class, string $expected): bool { - // 继承关系判断的唯一入口。调用者不应直接使用 PHP 运行时反射函数判断普通项目类。 - // 对 AOT 已扫描到的项目类/接口,必须走 classDef/interfaceDef 中的 extends/implements 图; - // 对 PHP 内置类/接口,可以使用 Zend 运行时反射,因为这部分属于目标 PHP 运行时的固定能力; - // 对动态类返回 true 表示“静态阶段无法否定”,后续必须保留运行时检查兜底。 + // The single entry point for inheritance checks. Callers must not use PHP + // runtime reflection functions directly to judge ordinary project classes. + // For project classes/interfaces already scanned by AOT, the extends/ + // implements graph in classDef/interfaceDef must be followed; for PHP + // built-in classes/interfaces, Zend runtime reflection may be used, since + // these are fixed capabilities of the target PHP runtime. Returning true + // for a dynamic class means "cannot be disproven at static time", so a + // runtime check must be retained as a fallback. $class = ltrim($class, '\\'); $expected = ltrim($expected, '\\'); if (strcasecmp($class, $expected) === 0) { @@ -51,15 +55,18 @@ trait NativeTypeCompatibilityTrait } if ($this->isInternalClass($class) or $this->isInternalInterface($class)) { - // 只允许内置类型之间使用 Zend 的继承关系。这里不是查询任意用户类, - // 因此不会把编译器进程加载过的外部库类混入项目静态类型系统。 + // Zend's inheritance relation is only used between built-in types. + // This is not a query on an arbitrary user class, so external library + // classes loaded by the compiler process are never mixed into the + // project's static type system. if (!$internal) { return false; } return is_subclass_of($class, $expected); } - // 类不存在,说明这是一个动态类,跳过静态检查,需要运行时检查 + // If the class does not exist, it is a dynamic class; skip the static + // check and defer to a runtime check if (!$this->hasClass($class)) { return true; } @@ -103,8 +110,9 @@ trait NativeTypeCompatibilityTrait return true; } if (!$this->hasClass($class)) { - // 原生类继承自一个内置类,例如: UserError extends Exception ,然后 $expected 预期是 Throwable - // 这种情况,需要使用 ZendVM 获取继承关系 + // A native class extends a built-in class (e.g. UserError extends + // Exception), and $expected is Throwable. In this case ZendVM must + // be used to obtain the inheritance relation. if ($this->isInternalClass($class) and $internal) { return $class === $expected or is_subclass_of($class, $expected); } @@ -116,8 +124,11 @@ trait NativeTypeCompatibilityTrait } $class = $classDef->extends; if ($this->isInternalClass($class)) { - // 项目类可以继承内置类。进入内置父类链后,后续关系交给 Zend 判断; - // 但 expected 也必须是内置类/接口,否则不能跨到外部用户类命名空间做运行时反射。 + // Project classes may extend built-in classes. Once the built-in + // parent chain is entered, further relations are delegated to Zend; + // however, $expected must also be a built-in class/interface, + // otherwise runtime reflection cannot cross into the external user + // class namespace. return $internal && is_subclass_of($class, $expected); } $classDef = $this->getClass($class); @@ -126,8 +137,10 @@ trait NativeTypeCompatibilityTrait private function interfaceExtends(string $interface, string $expected): bool { - // 接口继承需要单独处理,因为 interfaceDef 没有 classDef 的父类链。 - // 这里同样只遍历 AOT 已知接口图;遇到内置接口时,才允许使用 Zend 的 is_subclass_of()。 + // Interface inheritance is handled separately because interfaceDef has no + // parent chain like classDef. Only the AOT-known interface graph is + // traversed here; Zend's is_subclass_of() is allowed only when a built-in + // interface is encountered. $stack = [$interface]; while ($stack) { $check = array_pop($stack); @@ -228,7 +241,8 @@ trait NativeTypeCompatibilityTrait } if ($this->isVarExpr($arg->value)) { $var = $this->parseVariable($arg->value); - // 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用 + // For a by-reference parameter, an undefined variable may be passed; + // it is created immediately as a reference if (!$this->hasLocalVar($var)) { $this->addLocalVar($var, Type::VAR); } @@ -266,10 +280,13 @@ trait NativeTypeCompatibilityTrait if ($declaredClass !== '') { $class = $this->detectDeclaredClassOfExpr($arg->value); if ($class !== '') { - // native call 是性能热点,若静态阶段已经证明实参 is-a 声明类型, - // 就不要再生成 php::toObject($expr, target_ce) 做重复运行时检查。 - // 如果无法证明,但右值是已知 concrete object,说明一定不兼容,直接编译期 fatal; - // 其他动态/外部库/any 场景保留 php::toObject() 作为运行时兜底。 + // Native calls are a performance hot path. If the static phase + // has already proven the argument is-a the declared type, do not + // emit php::toObject($expr, target_ce) to repeat the runtime + // check. If it cannot be proven but the right-hand side is a + // known concrete object, it is necessarily incompatible, so fail + // at compile time; other dynamic/external-library/any scenarios + // keep php::toObject() as a runtime fallback. if ($this->isObjectClassStaticallyAssignableTo($class, $declaredClass)) { return $type === Type::OBJECT ? $expr : $this->convertObjectExpr($expr); } diff --git a/src/compiler.php b/src/compiler.php index d8f66778..c03e2fce 100644 --- a/src/compiler.php +++ b/src/compiler.php @@ -47,7 +47,7 @@ function main(int $argc, array $argv): void return; } - // .prof 文件分析模式:./tpc app.prof + // .prof file analysis mode: ./tpc app.prof if ($argc >= 2 && str_ends_with($argv[1], '.prof')) { profileAnalyze($argc, $argv); return; @@ -57,9 +57,9 @@ function main(int $argc, array $argv): void $translator = new Translator(TYPEPHP_ROOT_PATH); $translator->setIndent(' '); - // 扫描所有 PHP 文件,预处理 + // Scan all PHP files and preprocess them. $files = $translator->prepare($translator->parseArgv($argv)); - // 生成 C++ 文件 + // Generate the C++ source files. $sourceFiles = $translator->convert($files); $wasmManifest = getenv('TYPEPHP_WASM_INTERFACE_MANIFEST'); @@ -87,7 +87,7 @@ function main(int $argc, array $argv): void $sourceFiles[] = $wasmAdapter; } - // --dry 模式:仅生成 C++ 代码,不执行编译 + // --dry mode: only generate the C++ code, without compiling. if ($translator->isDryRun()) { $buildDir = $translator->getBuildDir(); $count = count($sourceFiles); @@ -105,11 +105,11 @@ function main(int $argc, array $argv): void return; } - // 编译所有 C++ 文件 + // Compile all C++ source files. $objectFiles = $translator->compile($sourceFiles); - // 连接所有目标文件,生成可执行文件 + // Link all object files to produce the executable. $binaryFile = $translator->build($objectFiles); - // 如果指定了 --run / -r,编译完成后立即执行 + // If --run / -r was specified, execute immediately after compilation. if ($translator->isRunRequested()) { $translator->run($binaryFile); // never returns } @@ -310,7 +310,7 @@ function profileAnalyze(int $argc, array $argv): void exit(1); } - // 从 prof 文件名推导二进制文件名(app.prof → app) + // Derive the binary name from the prof file name (app.prof → app). $binary = basename($profFile, '.prof'); if (!file_exists($binary) && file_exists('./' . $binary)) { $binary = './' . $binary; diff --git a/src/gen_stub.php b/src/gen_stub.php index d559806c..85547588 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -2494,7 +2494,8 @@ OUPUT_EXAMPLE return null; } foreach ($generatedFuncInfos as $generatedFuncInfo) { - // TODO 从数组遍历元素,调用方法,在编译期无法获得元素的类型,因此判断作用域,必须为 public 方法 + // TODO When iterating elements from an array and calling a method, the element type cannot be + // determined at compile time, so check the scope and require the method to be public. if ($generatedFuncInfo->equalsApartFromNameAndRefcount($this)) { return $generatedFuncInfo; } @@ -2695,7 +2696,8 @@ class EvaluatedValue $constType = ($const->phpDocType ?? $const->type)->tryToSimpleType(); if ($constType) { - // 这里返回的并不是真正的值,而是一个类型的占位符,最终的运算由编译器完成,此处仅用于 ArgInfo 处理 + // What is returned here is not the real value but a type placeholder; the final computation + // is performed by the compiler. This is only used for ArgInfo processing. if ($constType->isBool()) { return true; } elseif ($constType->isInt()) { @@ -3488,7 +3490,7 @@ class StringBuilder { $versions = [ PHP_85_VERSION_ID => self::PHP_85_KNOWN, PHP_84_VERSION_ID => self::PHP_84_KNOWN, - PHP_82_VERSION_ID => self::PHP_82_KNOWN, // 8.3 合并到 8.2 + PHP_82_VERSION_ID => self::PHP_82_KNOWN, // 8.3 is merged into 8.2 PHP_81_VERSION_ID => self::PHP_81_KNOWN, ]; @@ -3619,7 +3621,7 @@ class PropertyInfo extends VariableLike ); if (!$useEmptyArrayDefault) { - // New 操作作为属性的默认值,需编译器处理 gen_stub 作为 null 值 + // A New expression as a property default requires compiler handling; gen_stub treats it as a null value. if ($this->defaultValue === null || $this->defaultValue instanceof Expr\New_) { $defaultValue = EvaluatedValue::null(); } else { @@ -5041,7 +5043,7 @@ class FileInfo { $this->getMinimumPhpVersionIdCompatibility(), $this->isUndocumentable ); - // 清理当前类名,避免污染 + // Clear the current class name to avoid leaking it into subsequent classes. ClassInfo::$currentClass = ''; continue; } @@ -5253,7 +5255,7 @@ class FramelessFunctionInfo { } /** - * 获取魔术方法的默认返回值类型。只有用户未显式声明类型时才使用。 + * Get the default return type of a magic method. Used only when the user has not explicitly declared a type. */ function getMagicMethodDefaultReturnType(FunctionOrMethodName $name): ?string { @@ -5277,7 +5279,7 @@ function getMagicMethodDefaultReturnType(FunctionOrMethodName $name): ?string } /** - * 获取魔术方法的默认参数类型。只有用户未显式声明类型时才使用。 + * Get the default parameter type of a magic method. Used only when the user has not explicitly declared a type. */ function getMagicMethodDefaultParamType(FunctionOrMethodName $name, int $index): ?string { @@ -5934,7 +5936,7 @@ function generateFunctionEntries(?Name $className, array $funcInfos, ?string $co $underscoreName = implode("_", $className->getParts()); $functionEntryName = "class_{$underscoreName}_methods"; } else { - // 跳过生成 ext_functions + // Skip generating ext_functions. $functionEntryName = "ext_functions"; return ''; }