|
|
|
@ -866,15 +866,36 @@ CODE; |
|
|
|
|
|
|
|
|
|
|
|
$code .= '// static property ' . PHP_EOL; |
|
|
|
$code .= '// static property ' . PHP_EOL; |
|
|
|
foreach ($this->symbols->classes() as $classDef) { |
|
|
|
foreach ($this->symbols->classes() as $classDef) { |
|
|
|
|
|
|
|
// Traits are never instantiated on their own; their static properties |
|
|
|
|
|
|
|
// live on the classes that use them (where the members are flattened). |
|
|
|
|
|
|
|
// Initialising a default on the trait itself would write to the trait's |
|
|
|
|
|
|
|
// static property table and, on PHP >= 8.3, trigger a |
|
|
|
|
|
|
|
// "Accessing static trait property" deprecation when the value is read |
|
|
|
|
|
|
|
// through `self::` from a consuming class. Skip traits here; the |
|
|
|
|
|
|
|
// consuming classes still initialise their own (flattened) copies. |
|
|
|
|
|
|
|
if ($classDef->trait) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
if (!$property->isStatic() || !$property->arrayInitPlan || !$property->default) { |
|
|
|
if (!$property->isStatic() || $property->default === null) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
$statement = 'php::setStaticProperty(' |
|
|
|
if ($property->arrayInitPlan) { |
|
|
|
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', ' |
|
|
|
$statement = 'php::setStaticProperty(' |
|
|
|
. $this->genCharPtr($property->name) . ', ' |
|
|
|
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', ' |
|
|
|
. $property->arrayInitPlan->expr . ');' . PHP_EOL; |
|
|
|
. $this->genCharPtr($property->name) . ', ' |
|
|
|
$code .= $this->wrapArrayInitPlan($property->arrayInitPlan, $statement); |
|
|
|
. $property->arrayInitPlan->expr . ');' . PHP_EOL; |
|
|
|
|
|
|
|
$code .= $this->wrapArrayInitPlan($property->arrayInitPlan, $statement); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
$default = $property->type === Type::FLOAT |
|
|
|
|
|
|
|
? $this->convertFloatExpr($property->default) |
|
|
|
|
|
|
|
: $property->default; |
|
|
|
|
|
|
|
$statement = 'php::setStaticProperty(' |
|
|
|
|
|
|
|
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', ' |
|
|
|
|
|
|
|
. $this->genCharPtr($property->name) . ', ' |
|
|
|
|
|
|
|
. 'php::Var(' . $default . '));' . PHP_EOL; |
|
|
|
|
|
|
|
$code .= $statement; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1609,19 +1630,63 @@ CODE; |
|
|
|
if ($classDef && !$classDef->trait && !$classDef->enum) { |
|
|
|
if ($classDef && !$classDef->trait && !$classDef->enum) { |
|
|
|
$className = $classDef->getNamespacedName(); |
|
|
|
$className = $classDef->getNamespacedName(); |
|
|
|
$handlers = "property_handlers_{$className}"; |
|
|
|
$handlers = "property_handlers_{$className}"; |
|
|
|
$buildCreateBody = function (bool $attachHandlers) use ($classDef, $className, $handlers): string { |
|
|
|
$initBlock = ''; |
|
|
|
|
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
|
|
|
|
if ($property->isStatic() || $property->default === null) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if ($property->arrayInitPlan) { |
|
|
|
|
|
|
|
$init = "auto value = {$property->arrayInitPlan->expr};\n"; |
|
|
|
|
|
|
|
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; |
|
|
|
|
|
|
|
$init .= "php::throwErrorIfOccurred();\n"; |
|
|
|
|
|
|
|
$initBlock .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Scalar / constant / null default value. Wrap it in a |
|
|
|
|
|
|
|
// php::Var so it can be stored as a zval in the object's |
|
|
|
|
|
|
|
// property table via zend_update_property. Each property is |
|
|
|
|
|
|
|
// wrapped in its own block so the local `value` does not |
|
|
|
|
|
|
|
// clash with siblings declared in the same create_object body. |
|
|
|
|
|
|
|
$default = $property->type === Type::FLOAT |
|
|
|
|
|
|
|
? $this->convertFloatExpr($property->default) |
|
|
|
|
|
|
|
: $property->default; |
|
|
|
|
|
|
|
$init = "do {\n"; |
|
|
|
|
|
|
|
$init .= "auto value = php::Var({$default});\n"; |
|
|
|
|
|
|
|
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; |
|
|
|
|
|
|
|
$init .= "php::throwErrorIfOccurred();\n"; |
|
|
|
|
|
|
|
$init .= "} while (0);\n"; |
|
|
|
|
|
|
|
$initBlock .= $init; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$buildCreateBody = function (bool $attachHandlers) use ($classDef, $className, $handlers, $ce, $initBlock): string { |
|
|
|
$body = $classDef->ctorInit; |
|
|
|
$body = $classDef->ctorInit; |
|
|
|
$body .= "auto obj = create_object_{$className}(class_type);\n"; |
|
|
|
|
|
|
|
if ($attachHandlers) { |
|
|
|
if ($attachHandlers) { |
|
|
|
|
|
|
|
// PHP < 8.4: the custom handlers are attached to the object |
|
|
|
|
|
|
|
// AFTER the standard create_object, so object_properties_init |
|
|
|
|
|
|
|
// runs with the standard handlers (no asymmetric check). Our |
|
|
|
|
|
|
|
// explicit default inits run with the custom handlers |
|
|
|
|
|
|
|
// attached, so we set EG(fake_scope) to the object's own class |
|
|
|
|
|
|
|
// to satisfy asymmetric visibility for the class's own |
|
|
|
|
|
|
|
// properties. |
|
|
|
|
|
|
|
$body .= "auto obj = create_object_{$className}(class_type);\n"; |
|
|
|
$body .= "typephp_attach_property_handlers(obj, &{$handlers});\n"; |
|
|
|
$body .= "typephp_attach_property_handlers(obj, &{$handlers});\n"; |
|
|
|
} |
|
|
|
$body .= "zend_class_entry *__typephp_saved_fake_scope = EG(fake_scope);\n"; |
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
$body .= "EG(fake_scope) = obj->ce;\n"; |
|
|
|
if (!$property->isStatic() && $property->arrayInitPlan && $property->default) { |
|
|
|
$body .= $initBlock; |
|
|
|
$init = "auto value = {$property->arrayInitPlan->expr};\n"; |
|
|
|
$body .= "EG(fake_scope) = __typephp_saved_fake_scope;\n"; |
|
|
|
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; |
|
|
|
} else { |
|
|
|
$init .= "php::throwErrorIfOccurred();\n"; |
|
|
|
// PHP >= 8.4: the custom handlers live in |
|
|
|
$body .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init); |
|
|
|
// default_object_handlers, so the object already carries the |
|
|
|
} |
|
|
|
// asymmetric write_property hook at creation time and |
|
|
|
|
|
|
|
// object_properties_init would reject private(set)/protected(set) |
|
|
|
|
|
|
|
// default values (including inherited ones). Create the object |
|
|
|
|
|
|
|
// with the standard handlers, run the default initialization |
|
|
|
|
|
|
|
// (no visibility check), then attach the custom handlers. |
|
|
|
|
|
|
|
$body .= "auto obj = zend_objects_new(class_type);\n"; |
|
|
|
|
|
|
|
$body .= "obj->handlers = const_cast<zend_object_handlers *>(zend_get_std_object_handlers());\n"; |
|
|
|
|
|
|
|
$body .= "object_properties_init(obj, class_type);\n"; |
|
|
|
|
|
|
|
$body .= $initBlock; |
|
|
|
|
|
|
|
$body .= "obj->handlers = &{$handlers};\n"; |
|
|
|
} |
|
|
|
} |
|
|
|
$body .= $classDef->ctorClean; |
|
|
|
$body .= $classDef->ctorClean; |
|
|
|
return $body . "return obj;\n"; |
|
|
|
return $body . "return obj;\n"; |
|
|
|
@ -1633,7 +1698,7 @@ CODE; |
|
|
|
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; |
|
|
|
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; |
|
|
|
$code .= $buildCreateBody(true); |
|
|
|
$code .= $buildCreateBody(true); |
|
|
|
$code .= "};\n"; |
|
|
|
$code .= "};\n"; |
|
|
|
if ($classDef->requireCtor) { |
|
|
|
if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) { |
|
|
|
$code .= "#else\n"; |
|
|
|
$code .= "#else\n"; |
|
|
|
$code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; |
|
|
|
$code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; |
|
|
|
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; |
|
|
|
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; |
|
|
|
@ -1646,6 +1711,46 @@ CODE; |
|
|
|
return $code; |
|
|
|
return $code; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Whether the given class (or any of its ancestors) declares an asymmetric |
|
|
|
|
|
|
|
* visibility property (private(set)/protected(set)) or a hooked property |
|
|
|
|
|
|
|
* (getter/setter). Such classes install a custom write_property handler, and |
|
|
|
|
|
|
|
* on PHP >= 8.4 that handler lives in the class's default object handlers, so |
|
|
|
|
|
|
|
* the engine's object_properties_init would reject inherited default values |
|
|
|
|
|
|
|
* unless we generate our own create_object that initializes with the standard |
|
|
|
|
|
|
|
* handlers first. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private function classHasAsymmetricOrHookedProperty(ClassDef $classDef): bool |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
$current = $classDef; |
|
|
|
|
|
|
|
$seen = []; |
|
|
|
|
|
|
|
while ($current !== null) { |
|
|
|
|
|
|
|
$key = strtolower(ltrim($current->getNamespacedName(), '\\')); |
|
|
|
|
|
|
|
if (isset($seen[$key])) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
$seen[$key] = true; |
|
|
|
|
|
|
|
foreach ($current->properties as $property) { |
|
|
|
|
|
|
|
if ($property->isPrivateSet() |
|
|
|
|
|
|
|
|| $property->isProtectedSet() |
|
|
|
|
|
|
|
|| $property->getter !== null |
|
|
|
|
|
|
|
|| $property->setter !== null |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!$current->extends) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
$parent = $this->getClassDef($current->extends); |
|
|
|
|
|
|
|
if ($parent === null) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
$current = $parent; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function getRegisterClassFunction(string $name): string |
|
|
|
protected function getRegisterClassFunction(string $name): string |
|
|
|
{ |
|
|
|
{ |
|
|
|
return self::PREFIX . 'register_class_' . $name; |
|
|
|
return self::PREFIX . 'register_class_' . $name; |
|
|
|
@ -2884,13 +2989,13 @@ CODE; |
|
|
|
|
|
|
|
|
|
|
|
// 接口没有方法实体 |
|
|
|
// 接口没有方法实体 |
|
|
|
if ($classDef instanceof ClassDef) { |
|
|
|
if ($classDef instanceof ClassDef) { |
|
|
|
$arrayPropCount = 0; |
|
|
|
$defaultPropCount = 0; |
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
if ($property->type === Type::ARRAY && $property->arrayInitPlan && $property->default && !$property->isStatic()) { |
|
|
|
if (!$property->isStatic() && $property->default !== null) { |
|
|
|
$arrayPropCount++; |
|
|
|
$defaultPropCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if ($arrayPropCount > 0) { |
|
|
|
if ($defaultPropCount > 0) { |
|
|
|
$classDef->requireCtor = true; |
|
|
|
$classDef->requireCtor = true; |
|
|
|
} |
|
|
|
} |
|
|
|
$methods = $classDef->methods; |
|
|
|
$methods = $classDef->methods; |
|
|
|
|